From 7c1fe18b2cebe97883657ae0b642f7912c58fbf4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jan 2018 02:55:57 +0100 Subject: [PATCH] Fix and optimize Autocrypt 1.0 logic --- .../AutocryptPeerDataAccessObject.java | 116 +++++++----- .../keychain/provider/KeychainContract.java | 4 + .../keychain/provider/KeychainDatabase.java | 3 + .../keychain/provider/KeychainProvider.java | 10 +- .../remote/KeychainExternalProvider.java | 168 +++++++++++++----- 5 files changed, 207 insertions(+), 94 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java index db76b3f12..76895496b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java @@ -19,12 +19,13 @@ package org.sufficientlysecure.keychain.provider; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.database.DatabaseUtils; import android.net.Uri; import android.text.format.DateUtils; @@ -35,6 +36,7 @@ public class AutocryptPeerDataAccessObject { private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS; private static final String[] PROJECTION_AUTOCRYPT_QUERY = { + ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.LAST_SEEN_KEY, @@ -48,18 +50,19 @@ public class AutocryptPeerDataAccessObject { ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED, ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED, }; - private static final int INDEX_LAST_SEEN = 0; - private static final int INDEX_MASTER_KEY_ID = 1; - private static final int INDEX_LAST_SEEN_KEY = 2; - private static final int INDEX_STATE = 3; - private static final int INDEX_KEY_IS_REVOKED = 4; - private static final int INDEX_KEY_IS_EXPIRED = 5; - private static final int INDEX_KEY_IS_VERIFIED = 6; - private static final int INDEX_GOSSIP_MASTER_KEY_ID = 7; - private static final int INDEX_GOSSIP_LAST_SEEN_KEY = 8; - private static final int INDEX_GOSSIP_KEY_IS_REVOKED = 9; - private static final int INDEX_GOSSIP_KEY_IS_EXPIRED = 10; - private static final int INDEX_GOSSIP_KEY_IS_VERIFIED = 11; + private static final int INDEX_IDENTIFIER = 0; + private static final int INDEX_LAST_SEEN = 1; + private static final int INDEX_MASTER_KEY_ID = 2; + private static final int INDEX_LAST_SEEN_KEY = 3; + private static final int INDEX_STATE = 4; + private static final int INDEX_KEY_IS_REVOKED = 5; + private static final int INDEX_KEY_IS_EXPIRED = 6; + private static final int INDEX_KEY_IS_VERIFIED = 7; + private static final int INDEX_GOSSIP_MASTER_KEY_ID = 8; + private static final int INDEX_GOSSIP_LAST_SEEN_KEY = 9; + private static final int INDEX_GOSSIP_KEY_IS_REVOKED = 10; + private static final int INDEX_GOSSIP_KEY_IS_EXPIRED = 11; + private static final int INDEX_GOSSIP_KEY_IS_VERIFIED = 12; private final SimpleContentResolverInterface queryInterface; private final String packageName; @@ -178,7 +181,7 @@ public class AutocryptPeerDataAccessObject { queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null); } - public AutocryptPeerStateResult getAutocryptState(String autocryptId) { + public Map getAutocryptState(String... autocryptIds) { /* Determine if encryption is possible If there is no peers[to-addr], then set ui-recommendation to disable, and terminate. @@ -199,47 +202,70 @@ If autocrypt_timestamp is more than 35 days older than last_seen, set preliminar Otherwise, set preliminary-recommendation to available. */ - Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), - PROJECTION_AUTOCRYPT_QUERY, null, null, null); + Map result = new HashMap<>(); + StringBuilder selection = new StringBuilder(ApiAutocryptPeer.IDENTIFIER + " IN (?"); + for (int i = 1; i < autocryptIds.length; i++) { + selection.append(",?"); + } + selection.append(")"); + + Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageName(packageName), + PROJECTION_AUTOCRYPT_QUERY, selection.toString(), autocryptIds, null); try { - if (!cursor.moveToFirst()) { - return new AutocryptPeerStateResult(AutocryptState.DISABLE, null, false); - } + while (cursor.moveToNext()) { + String autocryptId = cursor.getString(INDEX_IDENTIFIER); - boolean hasKey = !cursor.isNull(INDEX_MASTER_KEY_ID); - boolean isRevoked = cursor.getInt(INDEX_KEY_IS_REVOKED) != 0; - boolean isExpired = cursor.getInt(INDEX_KEY_IS_EXPIRED) != 0; - if (hasKey && !isRevoked && !isExpired) { - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - long lastSeen = cursor.getLong(INDEX_LAST_SEEN); - long lastSeenKey = cursor.getLong(INDEX_LAST_SEEN_KEY); - boolean isVerified = cursor.getInt(INDEX_KEY_IS_VERIFIED) != 0; - if (lastSeenKey < (lastSeen - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) { - return new AutocryptPeerStateResult(AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified); + boolean hasKey = !cursor.isNull(INDEX_MASTER_KEY_ID); + boolean isRevoked = cursor.getInt(INDEX_KEY_IS_REVOKED) != 0; + boolean isExpired = cursor.getInt(INDEX_KEY_IS_EXPIRED) != 0; + if (hasKey && !isRevoked && !isExpired) { + long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); + long lastSeen = cursor.getLong(INDEX_LAST_SEEN); + long lastSeenKey = cursor.getLong(INDEX_LAST_SEEN_KEY); + boolean isVerified = cursor.getInt(INDEX_KEY_IS_VERIFIED) != 0; + if (lastSeenKey < (lastSeen - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) { + AutocryptPeerStateResult peerResult = new AutocryptPeerStateResult( + AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified); + result.put(autocryptId, peerResult); + continue; + } + + boolean isMutual = cursor.getInt(INDEX_STATE) != 0; + if (isMutual) { + AutocryptPeerStateResult peerResult = new AutocryptPeerStateResult( + AutocryptState.MUTUAL, masterKeyId, isVerified); + result.put(autocryptId, peerResult); + continue; + } else { + AutocryptPeerStateResult peerResult = new AutocryptPeerStateResult( + AutocryptState.AVAILABLE, masterKeyId, isVerified); + result.put(autocryptId, peerResult); + continue; + } } - boolean isMutual = cursor.getInt(INDEX_STATE) != 0; - if (isMutual) { - return new AutocryptPeerStateResult(AutocryptState.MUTUAL, masterKeyId, isVerified); - } else { - return new AutocryptPeerStateResult(AutocryptState.AVAILABLE, masterKeyId, isVerified); + boolean gossipHasKey = !cursor.isNull(INDEX_GOSSIP_MASTER_KEY_ID); + boolean gossipIsRevoked = cursor.getInt(INDEX_GOSSIP_KEY_IS_REVOKED) != 0; + boolean gossipIsExpired = cursor.getInt(INDEX_GOSSIP_KEY_IS_EXPIRED) != 0; + boolean isVerified = cursor.getInt(INDEX_GOSSIP_KEY_IS_VERIFIED) != 0; + if (gossipHasKey && !gossipIsRevoked && !gossipIsExpired) { + long masterKeyId = cursor.getLong(INDEX_GOSSIP_MASTER_KEY_ID); + AutocryptPeerStateResult peerResult = + new AutocryptPeerStateResult( + AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified); + result.put(autocryptId, peerResult); + continue; } - } - boolean gossipHasKey = !cursor.isNull(INDEX_GOSSIP_MASTER_KEY_ID); - boolean gossipIsRevoked = cursor.getInt(INDEX_GOSSIP_KEY_IS_REVOKED) != 0; - boolean gossipIsExpired = cursor.getInt(INDEX_GOSSIP_KEY_IS_EXPIRED) != 0; - boolean isVerified = cursor.getInt(INDEX_GOSSIP_KEY_IS_VERIFIED) != 0; - if (gossipHasKey && !gossipIsRevoked && !gossipIsExpired) { - System.err.println("xx"); - long masterKeyId = cursor.getLong(INDEX_GOSSIP_MASTER_KEY_ID); - return new AutocryptPeerStateResult(AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified); + AutocryptPeerStateResult peerResult = new AutocryptPeerStateResult( + AutocryptState.DISABLE, null, false); + result.put(autocryptId, peerResult); } - - return new AutocryptPeerStateResult(AutocryptState.DISABLE, null, false); } finally { cursor.close(); } + + return result; } public static class AutocryptPeerStateResult { 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 afa90700d..7062d6179 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -386,6 +386,10 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).build(); } + public static Uri buildByPackageName(String packageName) { + return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).build(); + } + public static Uri buildByPackageNameAndAutocryptId(String packageName, String autocryptPeer) { return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).appendPath(autocryptPeer).build(); } 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 acace5b58..40955479c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -236,6 +236,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX verified_certs ON certs (" + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");"); + db.execSQL("CREATE INDEX uids_by_email ON user_packets (" + + UserPacketsColumns.EMAIL + ");"); Preferences.getPreferences(mContext).setKeySignaturesTableInitialized(); } @@ -412,6 +414,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { case 24: db.execSQL("DROP TABLE api_autocrypt_peers"); db.execSQL(CREATE_API_AUTOCRYPT_PEERS); + db.execSQL("CREATE INDEX uids_by_email ON user_packets (email);"); } } 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 03e36523c..af7369352 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -717,14 +717,10 @@ public class KeychainProvider extends ContentProvider { case AUTOCRYPT_PEERS_BY_MASTER_KEY_ID: case AUTOCRYPT_PEERS_BY_PACKAGE_NAME: case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { - if (selection != null || selectionArgs != null) { - throw new UnsupportedOperationException(); - } - long unixSeconds = new Date().getTime() / 1000; HashMap projectionMap = new HashMap<>(); - projectionMap.put(ApiAutocryptPeer._ID, "oid AS " + ApiAutocryptPeer._ID); + projectionMap.put(ApiAutocryptPeer._ID, Tables.API_AUTOCRYPT_PEERS + ".oid AS " + ApiAutocryptPeer._ID); projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER); projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME); projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN); @@ -771,8 +767,8 @@ public class KeychainProvider extends ContentProvider { } else if (match == AUTOCRYPT_PEERS_BY_PACKAGE_NAME) { String packageName = uri.getPathSegments().get(2); - selection = Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = ?"; - selectionArgs = new String[] { packageName }; + qb.appendWhere(Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = "); + qb.appendWhereEscapeString(packageName); } else { // AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID String packageName = uri.getPathSegments().get(2); String autocryptPeer = uri.getPathSegments().get(3); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 4b45fc887..d0b17bb0f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -22,6 +22,7 @@ import java.security.AccessControlException; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import android.content.ContentProvider; import android.content.ContentValues; @@ -53,6 +54,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; +import org.sufficientlysecure.keychain.provider.KeychainProvider; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory; import timber.log.Timber; @@ -67,6 +69,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC private static final int API_APPS = 301; private static final int API_APPS_BY_PACKAGE_NAME = 302; + private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME = 602; + public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses"; public static final String TEMP_TABLE_COLUMN_ADDRES = "address"; @@ -99,6 +103,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC // can only query status of calling app - for internal use only! matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); + matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" + + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", AUTOCRYPT_PEERS_BY_PACKAGE_NAME); + return matcher; } @@ -138,6 +145,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); + long startTime = System.currentTimeMillis(); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); @@ -239,7 +247,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC TEMP_TABLE_COLUMN_ADDRES + " TEXT NOT NULL PRIMARY KEY, " + AutocryptStatus.UID_KEY_STATUS + " INT, " + AutocryptStatus.UID_ADDRESS + " TEXT, " + - AutocryptStatus.UID_MASTER_KEY_ID + " TEXT, " + + AutocryptStatus.UID_MASTER_KEY_ID + " INT, " + + AutocryptStatus.UID_CANDIDATES + " INT, " + AutocryptStatus.AUTOCRYPT_KEY_STATUS + " INT, " + AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT, " + AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + " INT" + @@ -250,48 +259,26 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv); } - long unixSeconds = System.currentTimeMillis() / 1000; - db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + - "(" + TEMP_TABLE_COLUMN_ADDRES + ", " + AutocryptStatus.UID_KEY_STATUS + ", " + AutocryptStatus.UID_ADDRESS + ")" + - " SELECT " + TEMP_TABLE_COLUMN_ADDRES + ", " + - "CASE ( MIN (certs_user_id." + Certs.VERIFIED + " ) ) " - // remap to keep this provider contract independent from our internal representation - + " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED - + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED - + " WHEN NULL THEN NULL" - + " END AS " + AutocryptStatus.UID_KEY_STATUS + - ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + - " FROM " + TEMP_TABLE_QUERIED_ADDRESSES - + " JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL" - + " AND " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " LIKE " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES - + ")" - + " JOIN " + Tables.CERTS + " AS certs_user_id ON (" - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = certs_user_id." + Certs.MASTER_KEY_ID - + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = certs_user_id." + Certs.RANK - + ")" - + " WHERE (EXISTS (SELECT * FROM " + Tables.KEYS + " WHERE " - + Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + " AND " + Tables.KEYS + "." + Keys.IS_REVOKED + " = 0" - + " AND NOT " + "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY - + " < " + unixSeconds + ")" - + ")) OR " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IS NULL" - + " GROUP BY " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES - ); + boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%"); - AutocryptPeerDataAccessObject autocryptPeerDao = - new AutocryptPeerDataAccessObject(getContext(), callingPackageName); - for (String autocryptId : selectionArgs) { - AutocryptPeerStateResult autocryptState = autocryptPeerDao.getAutocryptState(autocryptId); - cv.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, getPeerStateValue(autocryptState.autocryptState)); - if (autocryptState.masterKeyId != null) { - cv.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, autocryptState.masterKeyId); - cv.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, autocryptState.isVerified ? - KeychainExternalContract.KEY_STATUS_VERIFIED : KeychainExternalContract.KEY_STATUS_UNVERIFIED); - } - System.err.println(cv.toString()); - db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv, - TEMP_TABLE_COLUMN_ADDRES + "=?", new String [] { autocryptId }); + List plist = Arrays.asList(projection); + + boolean queriesUidResult = plist.contains(AutocryptStatus.UID_KEY_STATUS) || + plist.contains(AutocryptStatus.UID_ADDRESS) || + plist.contains(AutocryptStatus.UID_MASTER_KEY_ID) || + plist.contains(AutocryptStatus.UID_CANDIDATES); + if (queriesUidResult) { + fillTempTableWithUidResult(db, isWildcardSelector); + } + + boolean queriesAutocryptResult = plist.contains(AutocryptStatus.AUTOCRYPT_PEER_STATE) || + plist.contains(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID) || + plist.contains(AutocryptStatus.AUTOCRYPT_KEY_STATUS); + if (isWildcardSelector && queriesAutocryptResult) { + throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!"); + } + if (!isWildcardSelector && queriesAutocryptResult) { + fillTempTableWithAutocryptResult(selectionArgs, db, callingPackageName, cv); } qb.setTables(TEMP_TABLE_QUERIED_ADDRESSES); @@ -316,6 +303,11 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC break; } + case AUTOCRYPT_PEERS_BY_PACKAGE_NAME: + KeychainProvider keychainProvider = new KeychainProvider(); + keychainProvider.attachInfo(getContext(), null); + return keychainProvider.query(uri, projection, selection, selectionArgs, sortOrder); + default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -341,10 +333,102 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC } Timber.d("Query: " + qb.buildQuery(projection, selection, groupBy, null, orderBy, null)); + Timber.d(Constants.TAG, "Query took %s ms", (System.currentTimeMillis() - startTime)); return cursor; } + private void fillTempTableWithAutocryptResult(String[] selectionArgs, SQLiteDatabase db, String callingPackageName, + ContentValues cv) { + AutocryptPeerDataAccessObject autocryptPeerDao = + new AutocryptPeerDataAccessObject(this, callingPackageName); + Map autocryptStates = autocryptPeerDao.getAutocryptState(selectionArgs); + + for (String autocryptId : selectionArgs) { + cv.clear(); + + AutocryptPeerStateResult peerResult = autocryptStates.get(autocryptId); + if (peerResult == null) { + cv.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_PEER_DISABLED); + } else { + cv.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, getPeerStateValue(peerResult.autocryptState)); + if (peerResult.masterKeyId != null) { + cv.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, peerResult.masterKeyId); + cv.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, peerResult.isVerified ? + KeychainExternalContract.KEY_STATUS_VERIFIED : + KeychainExternalContract.KEY_STATUS_UNVERIFIED); + } + } + + db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", + new String[] { autocryptId }); + } + } + + private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) { + String cmpOperator = isWildcardSelector ? " LIKE " : " = "; + long unixSeconds = System.currentTimeMillis() / 1000; + db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + + "(" + TEMP_TABLE_COLUMN_ADDRES + ", " + AutocryptStatus.UID_KEY_STATUS + ", " + + AutocryptStatus.UID_ADDRESS + ", " + AutocryptStatus.UID_MASTER_KEY_ID + + ", " + AutocryptStatus.UID_CANDIDATES + ")" + + " SELECT " + TEMP_TABLE_COLUMN_ADDRES + ", " + + "CASE ( MIN (" + Tables.CERTS + "." + Certs.VERIFIED + " ) ) " + // remap to keep this provider contract independent from our internal representation + + " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED + + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED + + " END AS " + AutocryptStatus.UID_KEY_STATUS + + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + + ", " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + + ", COUNT(DISTINCT " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ")" + + " FROM " + TEMP_TABLE_QUERIED_ADDRESSES + + " LEFT JOIN " + Tables.USER_PACKETS + " ON (" + + Tables.USER_PACKETS + "." + UserPackets.EMAIL + cmpOperator + + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + + ")" + + " LEFT JOIN " + Tables.CERTS + " ON (" + + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + Tables.USER_PACKETS + "." + + UserPackets.MASTER_KEY_ID + + " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_PACKETS + "." + + UserPackets.RANK + + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + + ")" + + " WHERE (EXISTS (SELECT 1 FROM " + Tables.KEYS + " WHERE " + + Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.USER_PACKETS + "." + + UserPackets.MASTER_KEY_ID + + " AND " + Tables.KEYS + "." + Keys.RANK + " = 0" + + " AND " + Tables.KEYS + "." + Keys.IS_REVOKED + " = 0" + + " AND NOT " + "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + + "." + Keys.EXPIRY + + " < " + unixSeconds + ")" + + ")) OR " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IS NULL" + + " GROUP BY " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES); + } + + private void explainQuery(SQLiteDatabase db, String sql) { + Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + sql, new String[0]); + + // this is a debugging feature, we can be a little careless + explainCursor.moveToFirst(); + + StringBuilder line = new StringBuilder(); + for (int i = 0; i < explainCursor.getColumnCount(); i++) { + line.append(explainCursor.getColumnName(i)).append(", "); + } + Timber.d(Constants.TAG, line.toString()); + + while (!explainCursor.isAfterLast()) { + line = new StringBuilder(); + for (int i = 0; i < explainCursor.getColumnCount(); i++) { + line.append(explainCursor.getString(i)).append(", "); + } + Timber.d(Constants.TAG, line.toString()); + explainCursor.moveToNext(); + } + + explainCursor.close(); + } + private int getPeerStateValue(AutocryptState autocryptState) { switch (autocryptState) { case DISABLE: return AutocryptStatus.AUTOCRYPT_PEER_DISABLED;