From 5a2631841d3a98aff8111829b52bef63b8483dd4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 27 Oct 2017 14:52:04 +0200 Subject: [PATCH 01/14] add support for Autocrypt gossip key updates --- .../keychain/remote/OpenPgpService.java | 57 +++++++++++++++---- .../remote/OpenPgpServiceKeyIdExtractor.java | 2 +- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 4f889807f..207cf27ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -31,6 +31,7 @@ import java.util.List; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; +import android.os.Bundle; import android.os.IBinder; import android.os.Message; import android.os.Messenger; @@ -388,7 +389,7 @@ public class OpenPgpService extends Service { AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject( getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); - updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao); + updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao, false); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mKeyRepository, progressable); @@ -475,10 +476,18 @@ public class OpenPgpService extends Service { mApiPendingIntentFactory.createSecurityProblemIntent(packageName, securityProblem, supportOverride)); } - private String updateAutocryptPeerStateFromIntent(Intent data, AutocryptPeerDataAccessObject autocryptPeerDao) + private String updateAutocryptPeerStateFromIntent(Intent data, AutocryptPeerDataAccessObject autocryptPeerDao, + boolean isGossip) throws PgpGeneralException, IOException { String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE); + + return updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate, autocryptPeerDao, isGossip); + } + + private String updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate, + AutocryptPeerDataAccessObject autocryptPeerDao, boolean isGossip) + throws PgpGeneralException, IOException { if (autocryptPeerUpdate == null) { return null; } @@ -500,7 +509,7 @@ public class OpenPgpService extends Service { Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId); Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); if (newMasterKeyId == null) { - if (effectiveDate.after(lastSeen)) { + if (!isGossip && effectiveDate.after(lastSeen)) { autocryptPeerDao.updateToResetState(autocryptPeerId, effectiveDate); } return autocryptPeerId; @@ -512,7 +521,9 @@ public class OpenPgpService extends Service { } if (lastSeen == null || effectiveDate.after(lastSeen)) { - if (autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL) { + if (isGossip) { + autocryptPeerDao.updateToGossipState(autocryptPeerId, effectiveDate, newMasterKeyId); + } else if (autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL) { autocryptPeerDao.updateToMutualState(autocryptPeerId, effectiveDate, newMasterKeyId); } else { autocryptPeerDao.updateToAvailableState(autocryptPeerId, effectiveDate, newMasterKeyId); @@ -641,7 +652,23 @@ public class OpenPgpService extends Service { private Intent getKeyImpl(Intent data, OutputStream outputStream) { try { - long masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0); + long masterKeyId; + if (data.hasExtra(OpenPgpApi.EXTRA_KEY_ID)) { + masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0); + } else if (data.hasExtra(OpenPgpApi.EXTRA_USER_ID)) { + KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromEmails( + null, new String[] { data.getStringExtra(OpenPgpApi.EXTRA_USER_ID) }, + mApiPermissionHelper.getCurrentCallingPackage()); + if (keyIdResult.getStatus() != KeyIdResultStatus.OK) { + Intent result = getAutocryptStatusResult(keyIdResult); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); + return result; + } + + masterKeyId = keyIdResult.getKeyIds()[0]; + } else { + throw new IllegalArgumentException("Missing argument key_id or user_id!"); + } try { // try to find key, throws NotFoundException if not in db! @@ -784,15 +811,23 @@ public class OpenPgpService extends Service { private Intent updateAutocryptPeerImpl(Intent data) { try { - if (!data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) || - !data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) { - throw new IllegalArgumentException("need to specify both autocrypt_peer_id and autocrypt_peer_update!"); - } - AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); - updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao); + if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) && + data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) { + updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao, false); + } else if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) { + Bundle updates = data.getBundleExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES); + for (String address : updates.keySet()) { + Log.d(Constants.TAG, "Updating gossip state: " + address); + AutocryptPeerUpdate update = updates.getParcelable(address); + updateAutocryptPeerState(address, update, autocryptPeerentityDao, true); + } + } else { + throw new IllegalArgumentException("need to specify both autocrypt_peer_id and" + + "autocrypt_peer_update, or autocrypt_peer_gossip_updates!"); + } Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java index 7f38c555e..f3f9a6d61 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java @@ -102,7 +102,7 @@ class OpenPgpServiceKeyIdExtractor { return result; } - private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionAddresses, String callingPackageName) { + KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionAddresses, String callingPackageName) { boolean hasAddresses = (encryptionAddresses != null && encryptionAddresses.length > 0); boolean allKeysConfirmed = false; From 7b268b11edba1269cc9ba5d3c7ceeaa0334b7a67 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 8 Jan 2018 12:38:57 +0100 Subject: [PATCH 02/14] Fix up logic to match Autocrypt 1.0 --- .../AutocryptPeerDataAccessObject.java | 169 ++++++++++++++---- .../keychain/provider/KeychainContract.java | 20 ++- .../keychain/provider/KeychainDatabase.java | 14 +- .../provider/KeychainExternalContract.java | 13 +- .../keychain/provider/KeychainProvider.java | 78 ++++++-- .../keychain/remote/AutocryptInteractor.java | 106 +++++++++++ .../remote/KeychainExternalProvider.java | 137 +++++++------- .../keychain/remote/OpenPgpService.java | 139 +++++--------- .../remote/OpenPgpServiceKeyIdExtractor.java | 77 ++------ .../ui/dialog/RemoteDeduplicatePresenter.java | 4 +- .../remote/KeychainExternalProviderTest.java | 79 +++++--- 11 files changed, 515 insertions(+), 321 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java 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 549133408..db76b3f12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java @@ -24,13 +24,44 @@ 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; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; public class AutocryptPeerDataAccessObject { - private final SimpleContentResolverInterface mQueryInterface; + private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS; + + private static final String[] PROJECTION_AUTOCRYPT_QUERY = { + ApiAutocryptPeer.LAST_SEEN, + ApiAutocryptPeer.MASTER_KEY_ID, + ApiAutocryptPeer.LAST_SEEN_KEY, + ApiAutocryptPeer.IS_MUTUAL, + ApiAutocryptPeer.KEY_IS_REVOKED, + ApiAutocryptPeer.KEY_IS_EXPIRED, + ApiAutocryptPeer.KEY_IS_VERIFIED, + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, + ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, + ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED, + 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 final SimpleContentResolverInterface queryInterface; private final String packageName; @@ -38,7 +69,7 @@ public class AutocryptPeerDataAccessObject { this.packageName = packageName; final ContentResolver contentResolver = context.getContentResolver(); - mQueryInterface = new SimpleContentResolverInterface() { + queryInterface = new SimpleContentResolverInterface() { @Override public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { @@ -63,12 +94,12 @@ public class AutocryptPeerDataAccessObject { } public AutocryptPeerDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName) { - mQueryInterface = queryInterface; + this.queryInterface = queryInterface; this.packageName = packageName; } public Long getMasterKeyIdForAutocryptPeer(String autocryptId) { - Cursor cursor = mQueryInterface.query( + Cursor cursor = queryInterface.query( ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null, null, null); try { @@ -86,7 +117,7 @@ public class AutocryptPeerDataAccessObject { } public Date getLastSeen(String autocryptId) { - Cursor cursor = mQueryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), + Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null, null, null); try { @@ -103,7 +134,7 @@ public class AutocryptPeerDataAccessObject { } public Date getLastSeenKey(String autocryptId) { - Cursor cursor = mQueryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), + Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null, null, null); try { @@ -119,38 +150,112 @@ public class AutocryptPeerDataAccessObject { return null; } - public void updateToResetState(String autocryptId, Date effectiveDate) { - updateAutocryptState(autocryptId, effectiveDate, null, ApiAutocryptPeer.RESET); + public void updateLastSeen(String autocryptId, Date date) { + ContentValues cv = new ContentValues(); + cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime()); + queryInterface + .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); } - public void updateToSelectedState(String autocryptId, long masterKeyId) { - updateAutocryptState(autocryptId, new Date(), masterKeyId, ApiAutocryptPeer.SELECTED); - } - - public void updateToGossipState(String autocryptId, Date effectiveDate, long masterKeyId) { - updateAutocryptState(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP); - } - - public void updateToMutualState(String autocryptId, Date effectiveDate, long masterKeyId) { - updateAutocryptState(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.MUTUAL); - } - - public void updateToAvailableState(String autocryptId, Date effectiveDate, long masterKeyId) { - updateAutocryptState(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.AVAILABLE); - } - - private void updateAutocryptState(String autocryptId, Date date, Long masterKeyId, int status) { + public void updateKey(String autocryptId, Date effectiveDate, long masterKeyId, boolean isMutual) { ContentValues cv = new ContentValues(); cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); - cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime()); - if (masterKeyId != null) { - cv.put(ApiAutocryptPeer.LAST_SEEN_KEY, masterKeyId); - } - cv.put(ApiAutocryptPeer.STATE, status); - mQueryInterface.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); + cv.put(ApiAutocryptPeer.LAST_SEEN_KEY, effectiveDate.getTime()); + cv.put(ApiAutocryptPeer.IS_MUTUAL, isMutual ? 1 : 0); + queryInterface + .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); + } + + public void updateKeyGossip(String autocryptId, Date effectiveDate, long masterKeyId) { + ContentValues cv = new ContentValues(); + cv.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, masterKeyId); + cv.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, effectiveDate.getTime()); + queryInterface + .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); } public void delete(String autocryptId) { - mQueryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null); + queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null); + } + + public AutocryptPeerStateResult getAutocryptState(String autocryptId) { + /* +Determine if encryption is possible +If there is no peers[to-addr], then set ui-recommendation to disable, and terminate. + +For the purposes of the rest of this recommendation, if either public_key or gossip_key is revoked, expired, or otherwise known to be unusable for encryption, then treat that key as though it were null (not present). + +If both public_key and gossip_key are null, then set ui-recommendation to disable and terminate. + +Otherwise, we derive the recommendation using a two-phase algorithm. The first phase computes the preliminary-recommendation. + +Preliminary Recommendation +If public_key is null, then set target-keys[to-addr] to gossip_key and set preliminary-recommendation to discourage and skip to the Deciding to Encrypt by Default. + +Otherwise, set target-keys[to-addr] to public_key. + +If autocrypt_timestamp is more than 35 days older than last_seen, set preliminary-recommendation to discourage. + +Otherwise, set preliminary-recommendation to available. + */ + + Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), + PROJECTION_AUTOCRYPT_QUERY, null, null, null); + try { + if (!cursor.moveToFirst()) { + return new AutocryptPeerStateResult(AutocryptState.DISABLE, null, false); + } + + 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 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) { + System.err.println("xx"); + long masterKeyId = cursor.getLong(INDEX_GOSSIP_MASTER_KEY_ID); + return new AutocryptPeerStateResult(AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified); + } + + return new AutocryptPeerStateResult(AutocryptState.DISABLE, null, false); + } finally { + cursor.close(); + } + } + + public static class AutocryptPeerStateResult { + public final Long masterKeyId; + public final AutocryptState autocryptState; + public final boolean isVerified; + + AutocryptPeerStateResult(AutocryptState autocryptState, Long masterKeyId, boolean isVerified) { + this.autocryptState = autocryptState; + this.masterKeyId = masterKeyId; + this.isVerified = isVerified; + } + + } + + public enum AutocryptState { + DISABLE, DISCOURAGED_OLD, DISCOURAGED_GOSSIP, AVAILABLE, MUTUAL } } 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 01ae4b5dd..afa90700d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -103,9 +103,13 @@ public class KeychainContract { String PACKAGE_NAME = "package_name"; String IDENTIFIER = "identifier"; String LAST_SEEN = "last_updated"; - String LAST_SEEN_KEY = "last_seen_key"; - String STATE = "state"; + String MASTER_KEY_ID = "master_key_id"; + String LAST_SEEN_KEY = "last_seen_key"; + String IS_MUTUAL = "is_mutual"; + + String GOSSIP_MASTER_KEY_ID = "gossip_master_key_id"; + String GOSSIP_LAST_SEEN_KEY = "gossip_last_seen_key"; } public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY; @@ -371,12 +375,12 @@ public class KeychainContract { public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_AUTOCRYPT_PEERS).build(); - - public static final int RESET = 0; - public static final int GOSSIP = 1; - public static final int SELECTED = 2; - public static final int AVAILABLE = 3; - public static final int MUTUAL = 4; + public static final String KEY_IS_REVOKED = "key_is_revoked"; + public static final String KEY_IS_EXPIRED = "key_is_expired"; + public static final String KEY_IS_VERIFIED = "key_is_verified"; + public static final String GOSSIP_KEY_IS_REVOKED = "gossip_key_is_revoked"; + public static final String GOSSIP_KEY_IS_EXPIRED = "gossip_key_is_expired"; + public static final String GOSSIP_KEY_IS_VERIFIED = "gossip_key_is_verified"; public static Uri buildByKeyUri(Uri uri) { return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).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 c838b69a2..acace5b58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -54,7 +54,7 @@ import timber.log.Timber; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 24; + private static final int DATABASE_VERSION = 25; private Context mContext; public interface Tables { @@ -173,10 +173,12 @@ public class KeychainDatabase extends SQLiteOpenHelper { "CREATE TABLE IF NOT EXISTS " + Tables.API_AUTOCRYPT_PEERS + " (" + ApiAutocryptPeerColumns.PACKAGE_NAME + " TEXT NOT NULL, " + ApiAutocryptPeerColumns.IDENTIFIER + " TEXT NOT NULL, " - + ApiAutocryptPeerColumns.LAST_SEEN + " INTEGER NOT NULL, " - + ApiAutocryptPeerColumns.LAST_SEEN_KEY + " INTEGER NOT NULL, " - + ApiAutocryptPeerColumns.STATE + " INTEGER NOT NULL, " + + ApiAutocryptPeerColumns.LAST_SEEN + " INTEGER, " + + ApiAutocryptPeerColumns.LAST_SEEN_KEY + " INTEGER NULL, " + + ApiAutocryptPeerColumns.IS_MUTUAL + " INTEGER NULL, " + ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NULL, " + + ApiAutocryptPeerColumns.GOSSIP_MASTER_KEY_ID + " INTEGER NULL, " + + ApiAutocryptPeerColumns.GOSSIP_LAST_SEEN_KEY + " INTEGER NULL, " + "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", " + ApiAutocryptPeerColumns.IDENTIFIER + "), " + "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES " @@ -406,6 +408,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "PRIMARY KEY(master_key_id, signer_key_id), " + "FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE" + ")"); + + case 24: + db.execSQL("DROP TABLE api_autocrypt_peers"); + db.execSQL(CREATE_API_AUTOCRYPT_PEERS); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java index 80fa0e68e..e19332d92 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -63,14 +63,13 @@ public class KeychainExternalContract { public static final String AUTOCRYPT_MASTER_KEY_ID = "autocrypt_master_key_id"; public static final String AUTOCRYPT_KEY_STATUS = "autocrypt_key_status"; public static final String AUTOCRYPT_PEER_STATE = "autocrypt_peer_state"; - public static final String AUTOCRYPT_LAST_SEEN = "autocrypt_last_seen"; - public static final String AUTOCRYPT_LAST_SEEN_KEY = "autocrypt_last_seen_key"; - public static final int AUTOCRYPT_PEER_RESET = ApiAutocryptPeer.RESET; - public static final int AUTOCRYPT_PEER_GOSSIP = ApiAutocryptPeer.GOSSIP; - public static final int AUTOCRYPT_PEER_SELECTED = ApiAutocryptPeer.SELECTED; - public static final int AUTOCRYPT_PEER_AVAILABLE = ApiAutocryptPeer.AVAILABLE; - public static final int AUTOCRYPT_PEER_MUTUAL = ApiAutocryptPeer.MUTUAL; + public static final int AUTOCRYPT_PEER_DISABLED = 0; + public static final int AUTOCRYPT_PEER_DISCOURAGED_OLD = 10; + public static final int AUTOCRYPT_PEER_GOSSIP = 20; + public static final int AUTOCRYPT_PEER_AVAILABLE_EXTERNAL = 30; + public static final int AUTOCRYPT_PEER_AVAILABLE = 40; + public static final int AUTOCRYPT_PEER_MUTUAL = 50; public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() .appendPath(BASE_AUTOCRYPT_STATUS).build(); 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 6a26a5852..03e36523c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -721,15 +721,47 @@ public class KeychainProvider extends ContentProvider { throw new UnsupportedOperationException(); } + long unixSeconds = new Date().getTime() / 1000; + HashMap projectionMap = new HashMap<>(); projectionMap.put(ApiAutocryptPeer._ID, "oid AS " + ApiAutocryptPeer._ID); - projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME); projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER); - projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.MASTER_KEY_ID); + projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME); projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN); + projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID); + projectionMap.put(ApiAutocryptPeer.IS_MUTUAL, ApiAutocryptPeer.IS_MUTUAL); + projectionMap.put(ApiAutocryptPeer.LAST_SEEN_KEY, ApiAutocryptPeer.LAST_SEEN_KEY); + projectionMap.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID); + projectionMap.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY); + projectionMap.put(ApiAutocryptPeer.KEY_IS_REVOKED, "ac_key." + Keys.IS_REVOKED + " AS " + ApiAutocryptPeer.KEY_IS_REVOKED); + projectionMap.put(ApiAutocryptPeer.KEY_IS_EXPIRED, "(CASE" + + " WHEN ac_key." + Keys.EXPIRY + " IS NULL THEN 0" + + " WHEN ac_key." + Keys.EXPIRY + " > " + unixSeconds + " THEN 0" + + " ELSE 1 END) AS " + ApiAutocryptPeer.KEY_IS_EXPIRED); + projectionMap.put(ApiAutocryptPeer.KEY_IS_VERIFIED, "EXISTS (SELECT * FROM " + Tables.CERTS + + " WHERE " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + + " AND " + Certs.VERIFIED + " = " + Certs.VERIFIED_SECRET + + ") AS " + ApiAutocryptPeer.KEY_IS_VERIFIED); + projectionMap.put(ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED, + "gossip_key." + Keys.IS_REVOKED + " AS " + ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED); + projectionMap.put(ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED, "(CASE" + + " WHEN gossip_key." + Keys.EXPIRY + " IS NULL THEN 0" + + " WHEN gossip_key." + Keys.EXPIRY + " > " + unixSeconds + " THEN 0" + + " ELSE 1 END) AS " + ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED); + projectionMap.put(ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED, "EXISTS (SELECT * FROM " + Tables.CERTS + + " WHERE " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID + + " AND " + Certs.VERIFIED + " = " + Certs.VERIFIED_SECRET + + ") AS " + ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED); qb.setProjectionMap(projectionMap); - qb.setTables(Tables.API_AUTOCRYPT_PEERS); + qb.setTables(Tables.API_AUTOCRYPT_PEERS + + " LEFT JOIN " + Tables.KEYS + " AS ac_key" + + " ON (ac_key." + Keys.MASTER_KEY_ID + " = " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + ")" + + " LEFT JOIN " + Tables.KEYS + " AS gossip_key" + + " ON (gossip_key." + Keys.MASTER_KEY_ID + " = " + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID + ")" + ); if (match == AUTOCRYPT_PEERS_BY_MASTER_KEY_ID) { long masterKeyId = Long.parseLong(uri.getLastPathSegment()); @@ -1084,34 +1116,44 @@ public class KeychainProvider extends ContentProvider { break; } case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { - Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID); - if (masterKeyId == null) { - throw new IllegalArgumentException("master_key_id must be a non-null value!"); - } - ContentValues actualValues = new ContentValues(); String packageName = uri.getPathSegments().get(2); actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment()); - actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); Long newLastSeen = values.getAsLong(ApiAutocryptPeer.LAST_SEEN); if (newLastSeen != null) { actualValues.put(ApiAutocryptPeer.LAST_SEEN, newLastSeen); + db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); + break; } - if (values.containsKey(ApiAutocryptPeer.LAST_SEEN_KEY)) { - actualValues.put(ApiAutocryptPeer.LAST_SEEN_KEY, - values.getAsLong(ApiAutocryptPeer.LAST_SEEN_KEY)); - } - if (values.containsKey(ApiAutocryptPeer.STATE)) { - actualValues.put(ApiAutocryptPeer.STATE, - values.getAsLong(ApiAutocryptPeer.STATE)); + Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID); + if (masterKeyId != null) { + Long lastSeenKey = values.getAsLong(ApiAutocryptPeer.LAST_SEEN_KEY); + Long preferEncryptState = values.getAsLong(ApiAutocryptPeer.IS_MUTUAL); + + actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); + actualValues.put(ApiAutocryptPeer.LAST_SEEN_KEY, lastSeenKey); + actualValues.put(ApiAutocryptPeer.IS_MUTUAL, preferEncryptState); + + db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); + contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null); + break; } - contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null); + Long gossipMasterKeyId = values.getAsLong(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID); + if (gossipMasterKeyId != null) { + Long gossipLastSeenKey = values.getAsLong(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY); + + actualValues.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, gossipMasterKeyId); + actualValues.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, gossipLastSeenKey); + + db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); + contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(gossipMasterKeyId), null); + break; + } - db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); break; } default: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java new file mode 100644 index 000000000..bfdea532e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -0,0 +1,106 @@ +package org.sufficientlysecure.keychain.remote; + + +import java.io.IOException; +import java.util.Date; + +import android.content.Context; + +import org.openintents.openpgp.AutocryptPeerUpdate; +import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.util.Log; + + +class AutocryptInteractor { + + private AutocryptPeerDataAccessObject autocryptPeerDao; + private KeyWritableRepository keyWritableRepository; + + public static AutocryptInteractor getInstance(Context context, AutocryptPeerDataAccessObject autocryptPeerentityDao) { + KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(context); + + return new AutocryptInteractor(autocryptPeerentityDao, keyWritableRepository); + } + + private AutocryptInteractor(AutocryptPeerDataAccessObject autocryptPeerDao, + KeyWritableRepository keyWritableRepository) { + this.autocryptPeerDao = autocryptPeerDao; + this.keyWritableRepository = keyWritableRepository; + } + + void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) + throws PgpGeneralException, IOException { + if (autocryptPeerUpdate == null) { + return; + } + + Long newMasterKeyId; + if (autocryptPeerUpdate.hasKeyData()) { + UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData()); + if (uncachedKeyRing.isSecret()) { + Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring"); + return; + } + // this will merge if the key already exists - no worries! + keyWritableRepository.savePublicKeyRing(uncachedKeyRing); + newMasterKeyId = uncachedKeyRing.getMasterKeyId(); + } else { + newMasterKeyId = null; + } + + Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); + autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate); + + if (newMasterKeyId == null) { + return; + } + + Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); + if (lastSeenKey == null || effectiveDate.after(lastSeenKey)) { + boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL; + autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual); + } + } + + + void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) + throws PgpGeneralException, IOException { + if (autocryptPeerUpdate == null) { + return; + } + + Long newMasterKeyId; + if (autocryptPeerUpdate.hasKeyData()) { + UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData()); + if (uncachedKeyRing.isSecret()) { + Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring"); + return; + } + // this will merge if the key already exists - no worries! + keyWritableRepository.savePublicKeyRing(uncachedKeyRing); + newMasterKeyId = uncachedKeyRing.getMasterKeyId(); + } else { + newMasterKeyId = null; + } + + Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId); + Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); + if (newMasterKeyId == null) { + return; + } + + Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); + if (lastSeenKey != null && effectiveDate.before(lastSeenKey)) { + return; + } + + if (lastSeen == null || effectiveDate.after(lastSeen)) { + autocryptPeerDao.updateKeyGossip(autocryptPeerId, effectiveDate, newMasterKeyId); + } + } +} 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 c7581929e..4b45fc887 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.remote; import java.security.AccessControlException; import java.util.Arrays; -import java.util.Date; import java.util.HashMap; import java.util.List; @@ -40,9 +39,11 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptPeerStateResult; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; @@ -230,81 +231,70 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); } - db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" + TEMP_TABLE_COLUMN_ADDRES + " TEXT);"); + if (projection == null) { + throw new IllegalArgumentException("Please provide a projection!"); + } + + db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" + + 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.AUTOCRYPT_KEY_STATUS + " INT, " + + AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT, " + + AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + " INT" + + ");"); ContentValues cv = new ContentValues(); for (String address : selectionArgs) { cv.put(TEMP_TABLE_COLUMN_ADDRES, address); db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv); } - HashMap projectionMap = new HashMap<>(); - projectionMap.put(AutocryptStatus._ID, "email AS _id"); - projectionMap.put(AutocryptStatus.ADDRESS, // this is actually the queried address - TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + AutocryptStatus.ADDRESS); + 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 + ); - projectionMap.put(AutocryptStatus.UID_ADDRESS, - Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + AutocryptStatus.UID_ADDRESS); - // we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified" - projectionMap.put(AutocryptStatus.UID_KEY_STATUS, "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); - projectionMap.put(AutocryptStatus.UID_MASTER_KEY_ID, - Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + AutocryptStatus.UID_MASTER_KEY_ID); - projectionMap.put(AutocryptStatus.UID_CANDIDATES, - "COUNT(DISTINCT " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + - ") AS " + AutocryptStatus.UID_CANDIDATES); - - projectionMap.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, "CASE ( MIN (certs_autocrypt_peer." + 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.AUTOCRYPT_KEY_STATUS); - projectionMap.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, - Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + " AS " + AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID); - projectionMap.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, Tables.API_AUTOCRYPT_PEERS + "." + - ApiAutocryptPeer.STATE + " AS " + AutocryptStatus.AUTOCRYPT_LAST_SEEN_KEY); - projectionMap.put(AutocryptStatus.AUTOCRYPT_LAST_SEEN, Tables.API_AUTOCRYPT_PEERS + "." + - ApiAutocryptPeer.LAST_SEEN + " AS " + AutocryptStatus.AUTOCRYPT_LAST_SEEN); - projectionMap.put(AutocryptStatus.AUTOCRYPT_LAST_SEEN_KEY, Tables.API_AUTOCRYPT_PEERS + "." + - ApiAutocryptPeer.LAST_SEEN_KEY + " AS " + AutocryptStatus.AUTOCRYPT_LAST_SEEN_KEY); - qb.setProjectionMap(projectionMap); - - if (projection == null) { - throw new IllegalArgumentException("Please provide a projection!"); + 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 }); } - qb.setTables( - TEMP_TABLE_QUERIED_ADDRESSES - + " LEFT 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 - + ")" - + " LEFT 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 - + ")" - + " LEFT JOIN " + Tables.API_AUTOCRYPT_PEERS + " ON (" - + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.IDENTIFIER + " LIKE queried_addresses.address" - + " AND " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = \"" + callingPackageName + "\"" - + ")" - + " LEFT JOIN " + Tables.CERTS + " AS certs_autocrypt_peer ON (" - + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + " = certs_autocrypt_peer." + Certs.MASTER_KEY_ID - + ")" - ); - // in case there are multiple verifying certificates - groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES; - - // can't have an expired master key for the uid candidate - qb.appendWhere("(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 - + " < " + new Date().getTime() / 1000 + ")" - + ")) OR " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IS NULL"); + qb.setTables(TEMP_TABLE_QUERIED_ADDRESSES); if (TextUtils.isEmpty(sortOrder)) { sortOrder = AutocryptStatus.ADDRESS; @@ -355,6 +345,17 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC return cursor; } + private int getPeerStateValue(AutocryptState autocryptState) { + switch (autocryptState) { + case DISABLE: return AutocryptStatus.AUTOCRYPT_PEER_DISABLED; + case DISCOURAGED_OLD: return AutocryptStatus.AUTOCRYPT_PEER_DISCOURAGED_OLD; + case DISCOURAGED_GOSSIP: return AutocryptStatus.AUTOCRYPT_PEER_GOSSIP; + case AVAILABLE: return AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE; + case MUTUAL: return AutocryptStatus.AUTOCRYPT_PEER_MUTUAL; + } + throw new IllegalStateException("Unhandled case!"); + } + private void checkIfPackageBelongsToCaller(Context context, String requestedPackageName) { int callerUid = Binder.getCallingUid(); String[] callerPackageNames = context.getPackageManager().getPackagesForUid(callerUid); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 207cf27ce..75c52a1da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -42,7 +42,6 @@ import android.support.annotation.Nullable; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.openintents.openpgp.AutocryptPeerUpdate; -import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt; import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpError; @@ -65,17 +64,14 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; -import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.AutocryptState; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; @@ -203,21 +199,9 @@ public class OpenPgpService extends Service { private Intent encryptAndSignImpl(Intent data, InputStream inputStream, OutputStream outputStream, boolean sign, boolean isQueryAutocryptStatus) { try { - boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME); - if (originalFilename == null) { - originalFilename = ""; - } - PgpSignEncryptData.Builder pgpData = PgpSignEncryptData.builder() - .setEnableAsciiArmorOutput(asciiArmor) .setVersionHeader(null); - boolean enableCompression = data.getBooleanExtra(OpenPgpApi.EXTRA_ENABLE_COMPRESSION, true); - if (!enableCompression) { - pgpData.setCompressionAlgorithm(OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED); - } - if (sign) { Intent signKeyIdIntent = getSignKeyMasterId(data); // NOTE: Fallback to return account settings (Old API) @@ -240,13 +224,25 @@ public class OpenPgpService extends Service { KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false, mApiPermissionHelper.getCurrentCallingPackage()); - boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); KeyIdResultStatus keyIdResultStatus = keyIdResult.getStatus(); if (isQueryAutocryptStatus) { return getAutocryptStatusResult(keyIdResult); } + boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + pgpData.setEnableAsciiArmorOutput(asciiArmor); + + boolean enableCompression = data.getBooleanExtra(OpenPgpApi.EXTRA_ENABLE_COMPRESSION, true); + pgpData.setCompressionAlgorithm(enableCompression ? OpenKeychainCompressionAlgorithmTags.USE_DEFAULT : + OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED); + + String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME); + if (originalFilename == null) { + originalFilename = ""; + } + if (keyIdResult.hasKeySelectionPendingIntent()) { + boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); if ((keyIdResultStatus == KeyIdResultStatus.MISSING || keyIdResultStatus == KeyIdResultStatus.NO_KEYS || keyIdResultStatus == KeyIdResultStatus.NO_KEYS_ERROR) && isOpportunistic) { return createErrorResultIntent(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, @@ -309,8 +305,8 @@ public class OpenPgpService extends Service { result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, keyIdResult.isAllKeysConfirmed()); - AutocryptState combinedAutocryptState = keyIdResult.getCombinedAutocryptState(); - if (combinedAutocryptState == null) { + int combinedAutocryptState = keyIdResult.getAutocryptRecommendation(); + if (combinedAutocryptState == AutocryptStatus.AUTOCRYPT_PEER_DISABLED) { switch (keyIdResult.getStatus()) { case NO_KEYS: case NO_KEYS_ERROR: @@ -334,18 +330,17 @@ public class OpenPgpService extends Service { } switch (combinedAutocryptState) { - case EXTERNAL: - case SELECTED: - case GOSSIP: - case RESET: { + case AutocryptStatus.AUTOCRYPT_PEER_DISCOURAGED_OLD: + case AutocryptStatus.AUTOCRYPT_PEER_GOSSIP: { result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_DISCOURAGE); break; } - case AVAILABLE: { + case AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE_EXTERNAL: + case AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE: { result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_AVAILABLE); break; } - case MUTUAL: { + case AutocryptStatus.AUTOCRYPT_PEER_MUTUAL: { result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_MUTUAL); break; } @@ -387,9 +382,7 @@ public class OpenPgpService extends Service { byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); String senderAddress = data.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS); - AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject( - getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); - updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao, false); + updateAutocryptPeerImpl(data); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mKeyRepository, progressable); @@ -476,63 +469,6 @@ public class OpenPgpService extends Service { mApiPendingIntentFactory.createSecurityProblemIntent(packageName, securityProblem, supportOverride)); } - private String updateAutocryptPeerStateFromIntent(Intent data, AutocryptPeerDataAccessObject autocryptPeerDao, - boolean isGossip) - throws PgpGeneralException, IOException { - String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); - AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE); - - return updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate, autocryptPeerDao, isGossip); - } - - private String updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate, - AutocryptPeerDataAccessObject autocryptPeerDao, boolean isGossip) - throws PgpGeneralException, IOException { - if (autocryptPeerUpdate == null) { - return null; - } - - Long newMasterKeyId; - if (autocryptPeerUpdate.hasKeyData()) { - UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData()); - if (uncachedKeyRing.isSecret()) { - Timber.e("Found secret key in autocrypt id! - Ignoring"); - return null; - } - // this will merge if the key already exists - no worries! - KeyWritableRepository.create(this).savePublicKeyRing(uncachedKeyRing); - newMasterKeyId = uncachedKeyRing.getMasterKeyId(); - } else { - newMasterKeyId = null; - } - - Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId); - Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); - if (newMasterKeyId == null) { - if (!isGossip && effectiveDate.after(lastSeen)) { - autocryptPeerDao.updateToResetState(autocryptPeerId, effectiveDate); - } - return autocryptPeerId; - } - - Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); - if (lastSeenKey != null && effectiveDate.before(lastSeenKey)) { - return autocryptPeerId; - } - - if (lastSeen == null || effectiveDate.after(lastSeen)) { - if (isGossip) { - autocryptPeerDao.updateToGossipState(autocryptPeerId, effectiveDate, newMasterKeyId); - } else if (autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL) { - autocryptPeerDao.updateToMutualState(autocryptPeerId, effectiveDate, newMasterKeyId); - } else { - autocryptPeerDao.updateToAvailableState(autocryptPeerId, effectiveDate, newMasterKeyId); - } - } - - return autocryptPeerId; - } - private void processDecryptionResultForResultIntent(int targetApiVersion, Intent result, OpenPgpDecryptionResult decryptionResult) { if (targetApiVersion < API_VERSION_WITH_DECRYPTION_RESULT) { @@ -627,7 +563,7 @@ public class OpenPgpService extends Service { } private OpenPgpSignatureResult processAutocryptPeerInfoToSignatureResult(OpenPgpSignatureResult signatureResult, - String autocryptPeerentity) { + String autocryptPeerId) { boolean hasValidSignature = signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED || signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED; @@ -637,11 +573,17 @@ public class OpenPgpService extends Service { AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); - Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeerentity); + Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeerId); long masterKeyId = signatureResult.getKeyId(); if (autocryptPeerMasterKeyId == null) { - autocryptPeerentityDao.updateToGossipState(autocryptPeerentity, new Date(), masterKeyId); + // TODO +// Date now = new Date(); +// Date effectiveTime = signatureResult.getSignatureTimestamp(); +// if (effectiveTime.after(now)) { +// effectiveTime = now; +// } +// autocryptPeerentityDao.updateKeyGossip(autocryptPeerId, effectiveTime, masterKeyId); return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW); } else if (masterKeyId == autocryptPeerMasterKeyId) { return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK); @@ -811,22 +753,25 @@ public class OpenPgpService extends Service { private Intent updateAutocryptPeerImpl(Intent data) { try { - AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(), + AutocryptPeerDataAccessObject autocryptPeerDao = new AutocryptPeerDataAccessObject(getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); + AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getBaseContext(), autocryptPeerDao); if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) && data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) { - updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao, false); - } else if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) { + String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); + AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE); + + autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate); + } + + if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) { Bundle updates = data.getBundleExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES); for (String address : updates.keySet()) { - Log.d(Constants.TAG, "Updating gossip state: " + address); + Timber.d(Constants.TAG, "Updating gossip state: " + address); AutocryptPeerUpdate update = updates.getParcelable(address); - updateAutocryptPeerState(address, update, autocryptPeerentityDao, true); + autocryptInteractor.updateAutocryptPeerGossipState(address, update); } - } else { - throw new IllegalArgumentException("need to specify both autocrypt_peer_id and" + - "autocrypt_peer_update, or autocrypt_peer_gossip_updates!"); } Intent result = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java index f3f9a6d61..71aadde74 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceKeyIdExtractor.java @@ -31,7 +31,6 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import org.openintents.openpgp.util.OpenPgpApi; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -82,7 +81,7 @@ class OpenPgpServiceKeyIdExtractor { for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED)) { encryptKeyIds.add(keyId); } - result = createKeysOkResult(encryptKeyIds, false, null); + result = createKeysOkResult(encryptKeyIds, false, AutocryptStatus.AUTOCRYPT_PEER_DISABLED); } else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS) || askIfNoUserIdsProvided) { String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); result = returnKeyIdsFromEmails(data, userIds, callingPackageName); @@ -109,7 +108,7 @@ class OpenPgpServiceKeyIdExtractor { HashSet keyIds = new HashSet<>(); ArrayList missingEmails = new ArrayList<>(); ArrayList duplicateEmails = new ArrayList<>(); - AutocryptState combinedAutocryptState = null; + int combinedAutocryptState = AutocryptStatus.AUTOCRYPT_PEER_DISABLED; if (hasAddresses) { HashMap userIdEntries = getStatusMapForQueriedAddresses( @@ -129,18 +128,15 @@ class OpenPgpServiceKeyIdExtractor { anyKeyNotVerified = true; } - if (combinedAutocryptState == null) { - combinedAutocryptState = addressQueryResult.autocryptState; - } else { - combinedAutocryptState = combinedAutocryptState.combineWith(addressQueryResult.autocryptState); - } + combinedAutocryptState = combineAutocryptState( + combinedAutocryptState, addressQueryResult.autocryptState); continue; } if (addressQueryResult.uidMasterKeyId != null) { keyIds.add(addressQueryResult.uidMasterKeyId); - combinedAutocryptState = AutocryptState.EXTERNAL; + combinedAutocryptState = AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE_EXTERNAL; if (addressQueryResult.uidHasMultipleCandidates) { duplicateEmails.add(queriedAddress); @@ -178,6 +174,10 @@ class OpenPgpServiceKeyIdExtractor { return createKeysOkResult(keyIds, allKeysConfirmed, combinedAutocryptState); } + private int combineAutocryptState(int first, int second) { + return first < second ? first : second; + } + /** This method queries the KeychainExternalProvider for all addresses given in encryptionUserIds. * It returns a map with one UserIdStatus per queried address. If multiple key candidates exist, * the one with the highest verification status is selected. If two candidates with the same @@ -207,7 +207,7 @@ class OpenPgpServiceKeyIdExtractor { AddressQueryResult status = new AddressQueryResult( uidMasterKeyId, uidKeyStatus, uidHasMultipleCandidates, autocryptMasterKeyId, - autocryptKeyStatus, AutocryptState.fromDbValue(autocryptPeerStatus)); + autocryptKeyStatus, autocryptPeerStatus); keyRows.put(queryAddress, status); } @@ -223,10 +223,10 @@ class OpenPgpServiceKeyIdExtractor { private boolean uidHasMultipleCandidates; private final Long autocryptMasterKeyId; private final int autocryptKeyStatus; - private final AutocryptState autocryptState; + private final int autocryptState; AddressQueryResult(Long uidMasterKeyId, int uidKeyStatus, boolean uidHasMultipleCandidates, Long autocryptMasterKeyId, - int autocryptKeyStatus, AutocryptState autocryptState) { + int autocryptKeyStatus, int autocryptState) { this.uidMasterKeyId = uidMasterKeyId; this.uidKeyStatus = uidKeyStatus; this.uidHasMultipleCandidates = uidHasMultipleCandidates; @@ -236,56 +236,13 @@ class OpenPgpServiceKeyIdExtractor { } } - enum AutocryptState { - EXTERNAL, RESET, GOSSIP, SELECTED, AVAILABLE, MUTUAL; - - static AutocryptState fromDbValue(int state) { - switch (state) { - case ApiAutocryptPeer.RESET: - return RESET; - case ApiAutocryptPeer.AVAILABLE: - return AVAILABLE; - case ApiAutocryptPeer.SELECTED: - return SELECTED; - case ApiAutocryptPeer.GOSSIP: - return GOSSIP; - case ApiAutocryptPeer.MUTUAL: - return MUTUAL; - default: - throw new IllegalStateException(); - } - } - - public AutocryptState combineWith(AutocryptState other) { - if (this == EXTERNAL || other == EXTERNAL) { - return EXTERNAL; - } - if (this == RESET || other == RESET) { - return RESET; - } - if (this == GOSSIP || other == GOSSIP) { - return GOSSIP; - } - if (this == SELECTED || other == SELECTED) { - return SELECTED; - } - if (this == AVAILABLE || other == AVAILABLE) { - return AVAILABLE; - } - if (this == MUTUAL && other == MUTUAL) { - return MUTUAL; - } - throw new IllegalStateException("Bug: autocrypt states can't be combined!"); - } - } - static class KeyIdResult { private final PendingIntent mKeySelectionPendingIntent; private final HashSet mUserKeyIds; private final HashSet mExplicitKeyIds; private final KeyIdResultStatus mStatus; private final boolean mAllKeysConfirmed; - private final AutocryptState mCombinedAutocryptState; + private final int mCombinedAutocryptState; private KeyIdResult(PendingIntent keySelectionPendingIntent, KeyIdResultStatus keyIdResultStatus) { mKeySelectionPendingIntent = keySelectionPendingIntent; @@ -293,11 +250,11 @@ class OpenPgpServiceKeyIdExtractor { mAllKeysConfirmed = false; mStatus = keyIdResultStatus; mExplicitKeyIds = null; - mCombinedAutocryptState = null; + mCombinedAutocryptState = AutocryptStatus.AUTOCRYPT_PEER_DISABLED; } private KeyIdResult(HashSet keyIds, boolean allKeysConfirmed, KeyIdResultStatus keyIdResultStatus, - AutocryptState combinedAutocryptState) { + int combinedAutocryptState) { mKeySelectionPendingIntent = null; mUserKeyIds = keyIds; mAllKeysConfirmed = allKeysConfirmed; @@ -355,7 +312,7 @@ class OpenPgpServiceKeyIdExtractor { return mStatus; } - public AutocryptState getCombinedAutocryptState() { + int getAutocryptRecommendation() { return mCombinedAutocryptState; } } @@ -365,7 +322,7 @@ class OpenPgpServiceKeyIdExtractor { } private KeyIdResult createKeysOkResult(HashSet encryptKeyIds, boolean allKeysConfirmed, - AutocryptState combinedAutocryptState) { + int combinedAutocryptState) { return new KeyIdResult(encryptKeyIds, allKeysConfirmed, KeyIdResultStatus.OK, combinedAutocryptState); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index 377f76c04..8e2fc55ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -120,8 +120,8 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { return; } - long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); - autocryptPeerDao.updateToSelectedState(duplicateAddress, masterKeyId); + // long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); + // autocryptPeerDao.updateToSelectedState(duplicateAddress, masterKeyId); view.finish(); } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java index 2e8a62806..950d3f275 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java @@ -207,13 +207,13 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateToAvailableState("tid", new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.updateKey(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, - AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, - AutocryptStatus.AUTOCRYPT_PEER_STATE + AutocryptStatus.AUTOCRYPT_PEER_STATE, + AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID }, null, new String [] { AUTOCRYPT_PEER }, null ); @@ -223,9 +223,36 @@ public class KeychainExternalProviderTest { assertEquals("tid", cursor.getString(0)); assertTrue(cursor.isNull(1)); assertEquals(null, cursor.getString(2)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(3)); - assertEquals(KEY_ID_PUBLIC, cursor.getLong(4)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE, cursor.getInt(5)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE, cursor.getInt(3)); + assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(4)); + assertEquals(KEY_ID_PUBLIC, cursor.getLong(5)); + assertFalse(cursor.moveToNext()); + } + + @Test + public void testAutocryptStatus_autocryptPeer_withMutualKey() throws Exception { + insertSecretKeyringFrom("/test-keys/testring.sec"); + insertPublicKeyringFrom("/test-keys/testring.pub"); + + autocryptPeerDao.updateKey(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, true); + + Cursor cursor = contentResolver.query( + AutocryptStatus.CONTENT_URI, new String[] { + AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, + AutocryptStatus.AUTOCRYPT_PEER_STATE, + AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + }, + null, new String [] { AUTOCRYPT_PEER }, null + ); + + assertNotNull(cursor); + assertTrue(cursor.moveToFirst()); + assertEquals("tid", cursor.getString(0)); + assertTrue(cursor.isNull(1)); + assertEquals(null, cursor.getString(2)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_MUTUAL, cursor.getInt(3)); + assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(4)); + assertEquals(KEY_ID_PUBLIC, cursor.getLong(5)); assertFalse(cursor.moveToNext()); } @@ -234,13 +261,13 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateToAvailableState("tid", new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.updateKey("tid", new Date(), KEY_ID_PUBLIC, false); certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, - AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_PEER_STATE }, + AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_KEY_STATUS }, null, new String [] { AUTOCRYPT_PEER }, null ); @@ -249,8 +276,8 @@ public class KeychainExternalProviderTest { assertEquals("tid", cursor.getString(0)); assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); assertEquals(null, cursor.getString(2)); - assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(3)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE, cursor.getInt(4)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE, cursor.getInt(3)); + assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(4)); assertFalse(cursor.moveToNext()); } @@ -259,7 +286,7 @@ public class KeychainExternalProviderTest { Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, - AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_PEER_STATE }, + AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_KEY_STATUS }, null, new String [] { AUTOCRYPT_PEER }, null ); @@ -268,8 +295,8 @@ public class KeychainExternalProviderTest { assertEquals("tid", cursor.getString(0)); assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); assertEquals(null, cursor.getString(2)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(3)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_RESET, cursor.getInt(4)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_DISABLED, cursor.getInt(3)); + assertTrue(cursor.isNull(4)); assertFalse(cursor.moveToNext()); } @@ -278,13 +305,13 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateToGossipState("tid", new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.updateKeyGossip("tid", new Date(), KEY_ID_PUBLIC); autocryptPeerDao.delete("tid"); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, - AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_PEER_STATE }, + AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_KEY_STATUS }, null, new String [] { AUTOCRYPT_PEER }, null ); @@ -293,8 +320,8 @@ public class KeychainExternalProviderTest { assertEquals("tid", cursor.getString(0)); assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); assertEquals(null, cursor.getString(2)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(3)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_RESET, cursor.getInt(4)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_DISABLED, cursor.getInt(3)); + assertTrue(cursor.isNull(4)); assertFalse(cursor.moveToNext()); } @@ -303,13 +330,13 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateToGossipState("tid", new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.updateKeyGossip(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC); certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, - AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_PEER_STATE }, + AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_KEY_STATUS }, null, new String [] { AUTOCRYPT_PEER }, null ); @@ -318,11 +345,12 @@ public class KeychainExternalProviderTest { assertEquals("tid", cursor.getString(0)); assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); assertEquals(null, cursor.getString(2)); - assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(3)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_GOSSIP, cursor.getInt(4)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_GOSSIP, cursor.getInt(3)); + assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(4)); assertFalse(cursor.moveToNext()); } +/* @Test public void testAutocryptStatus_stateSelected() throws Exception { insertSecretKeyringFrom("/test-keys/testring.sec"); @@ -334,7 +362,7 @@ public class KeychainExternalProviderTest { Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { AutocryptStatus.ADDRESS, AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_ADDRESS, - AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_PEER_STATE }, + AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_KEY_STATUS }, null, new String [] { AUTOCRYPT_PEER }, null ); @@ -343,10 +371,11 @@ public class KeychainExternalProviderTest { assertEquals("tid", cursor.getString(0)); assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); assertEquals(null, cursor.getString(2)); - assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(3)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_SELECTED, cursor.getInt(4)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_SELECTED, cursor.getInt(3)); + assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(4)); assertFalse(cursor.moveToNext()); } +*/ @Test public void testAutocryptStatus_withConfirmedKey() throws Exception { @@ -367,7 +396,7 @@ public class KeychainExternalProviderTest { assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(1)); assertEquals(USER_ID_1, cursor.getString(2)); - assertEquals(AutocryptStatus.AUTOCRYPT_PEER_RESET, cursor.getInt(3)); + assertEquals(AutocryptStatus.AUTOCRYPT_PEER_DISABLED, cursor.getInt(3)); assertFalse(cursor.moveToNext()); } From 7c1fe18b2cebe97883657ae0b642f7912c58fbf4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jan 2018 02:55:57 +0100 Subject: [PATCH 03/14] 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; From fe00c6727a61856c047c270ffdd7e06193d6bdff Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jan 2018 17:01:03 +0100 Subject: [PATCH 04/14] Only look at primary key for expiry/revocation --- .../keychain/provider/KeychainProvider.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 af7369352..d8d83eef3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -754,16 +754,18 @@ public class KeychainProvider extends ContentProvider { qb.setTables(Tables.API_AUTOCRYPT_PEERS + " LEFT JOIN " + Tables.KEYS + " AS ac_key" + - " ON (ac_key." + Keys.MASTER_KEY_ID + " = " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + ")" + + " ON (ac_key." + Keys.MASTER_KEY_ID + " = " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + + " AND ac_key." + Keys.RANK + " = 0)" + " LEFT JOIN " + Tables.KEYS + " AS gossip_key" + - " ON (gossip_key." + Keys.MASTER_KEY_ID + " = " + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID + ")" + " ON (gossip_key." + Keys.MASTER_KEY_ID + " = " + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID + + " AND gossip_key." + Keys.RANK + " = 0)" ); if (match == AUTOCRYPT_PEERS_BY_MASTER_KEY_ID) { long masterKeyId = Long.parseLong(uri.getLastPathSegment()); - selection = Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + " = ?"; - selectionArgs = new String[] { Long.toString(masterKeyId) }; + qb.appendWhere(Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + " = "); + qb.appendWhere(Long.toString(masterKeyId)); } else if (match == AUTOCRYPT_PEERS_BY_PACKAGE_NAME) { String packageName = uri.getPathSegments().get(2); From fcbaf23762c21dc95f0ef4a65b3de23204a2bd92 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jan 2018 17:01:21 +0100 Subject: [PATCH 05/14] better index for Keys table --- .../keychain/provider/KeychainDatabase.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 40955479c..f3b140fe1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -231,7 +231,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(CREATE_API_AUTOCRYPT_PEERS); - db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");"); + db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX verified_certs ON certs (" @@ -415,6 +415,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("DROP TABLE api_autocrypt_peers"); db.execSQL(CREATE_API_AUTOCRYPT_PEERS); db.execSQL("CREATE INDEX uids_by_email ON user_packets (email);"); + db.execSQL("DROP INDEX keys_by_rank"); + db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);"); } } From 1c3f9fd27fb70db8e03a4352ce81385ee281c593 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jan 2018 17:08:17 +0100 Subject: [PATCH 06/14] Clean up listing of Autocrypt along identities --- .../keychain/ui/adapter/IdentityAdapter.java | 14 ++--- .../ui/keyview/loader/IdentityLoader.java | 59 ++++++++++--------- .../presenter/IdentitiesPresenter.java | 10 ++-- 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java index 45eae6ffc..b02b61b5c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java @@ -39,7 +39,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.ViewHolder; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.LinkedIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.TrustIdInfo; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.AutocryptPeerInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.UserIdInfo; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -79,8 +79,8 @@ public class IdentityAdapter extends RecyclerView.Adapter { int viewType = getItemViewType(position); if (viewType == VIEW_TYPE_USER_ID) { - if (info instanceof TrustIdInfo) { - ((UserIdViewHolder) holder).bind((TrustIdInfo) info); + if (info instanceof AutocryptPeerInfo) { + ((UserIdViewHolder) holder).bind((AutocryptPeerInfo) info); } else { ((UserIdViewHolder) holder).bind((UserIdInfo) info); } @@ -107,7 +107,7 @@ public class IdentityAdapter extends RecyclerView.Adapter { @Override public int getItemViewType(int position) { IdentityInfo info = data.get(position); - if (info instanceof UserIdInfo || info instanceof TrustIdInfo) { + if (info instanceof UserIdInfo || info instanceof AutocryptPeerInfo) { return VIEW_TYPE_USER_ID; } else if (info instanceof LinkedIdInfo) { return VIEW_TYPE_LINKED_ID; @@ -236,21 +236,21 @@ public class IdentityAdapter extends RecyclerView.Adapter { }); } - public void bind(TrustIdInfo info) { + public void bind(AutocryptPeerInfo info) { if (info.getUserIdInfo() != null) { bindUserIdInfo(info.getUserIdInfo()); } else { vName.setVisibility(View.GONE); vComment.setVisibility(View.GONE); - vAddress.setText(info.getTrustId()); + vAddress.setText(info.getIdentity()); vAddress.setTypeface(null, Typeface.NORMAL); } vIcon.setImageDrawable(info.getAppIcon()); vMore.setVisibility(View.VISIBLE); - itemView.setClickable(info.getTrustIdIntent() != null); + itemView.setClickable(info.getAutocryptPeerIntent() != null); } public void bind(UserIdInfo info) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java index b2064b903..bafd1e74b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java @@ -31,14 +31,11 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import android.support.v4.content.AsyncTaskLoader; -import android.util.Log; import com.google.auto.value.AutoValue; import org.openintents.openpgp.util.OpenPgpApi; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.UriAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -76,6 +73,15 @@ public class IdentityLoader extends AsyncTaskLoader> { private static final String USER_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; + private static final String[] AUTOCRYPT_PEER_PROJECTION = new String[] { + ApiAutocryptPeer._ID, + ApiAutocryptPeer.PACKAGE_NAME, + ApiAutocryptPeer.IDENTIFIER, + }; + private static final int INDEX_PACKAGE_NAME = 1; + private static final int INDEX_IDENTIFIER = 2; + + private final ContentResolver contentResolver; private final PackageIconGetter packageIconGetter; private final long masterKeyId; @@ -85,6 +91,7 @@ public class IdentityLoader extends AsyncTaskLoader> { private ForceLoadContentObserver identityObserver; + public IdentityLoader(Context context, ContentResolver contentResolver, long masterKeyId, boolean showLinkedIds) { super(context); @@ -106,42 +113,36 @@ public class IdentityLoader extends AsyncTaskLoader> { loadLinkedIds(identities); } loadUserIds(identities); - correlateOrAddTrustIds(identities); + correlateOrAddAutocryptPeers(identities); return Collections.unmodifiableList(identities); } - private static final String[] TRUST_IDS_PROJECTION = new String[] { - ApiAutocryptPeer._ID, - ApiAutocryptPeer.PACKAGE_NAME, - ApiAutocryptPeer.IDENTIFIER, - }; - private static final int INDEX_PACKAGE_NAME = 1; - private static final int INDEX_TRUST_ID = 2; - - private void correlateOrAddTrustIds(ArrayList identities) { + private void correlateOrAddAutocryptPeers(ArrayList identities) { Cursor cursor = contentResolver.query(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId), - TRUST_IDS_PROJECTION, null, null, null); + AUTOCRYPT_PEER_PROJECTION, null, null, null); if (cursor == null) { - Timber.e("Error loading trust ids!"); + Timber.e("Error loading Autocrypt peers"); return; } try { while (cursor.moveToNext()) { String packageName = cursor.getString(INDEX_PACKAGE_NAME); - String autocryptPeer = cursor.getString(INDEX_TRUST_ID); + String autocryptPeer = cursor.getString(INDEX_IDENTIFIER); Drawable drawable = packageIconGetter.getDrawableForPackageName(packageName); - Intent autocryptPeerIntent = getTrustIdActivityIntentIfResolvable(packageName, autocryptPeer); + Intent autocryptPeerIntent = getAutocryptPeerActivityIntentIfResolvable(packageName, autocryptPeer); - UserIdInfo associatedUserIdInfo = findUserIdMatchingTrustId(identities, autocryptPeer); + UserIdInfo associatedUserIdInfo = findUserIdMatchingAutocryptPeer(identities, autocryptPeer); if (associatedUserIdInfo != null) { int position = identities.indexOf(associatedUserIdInfo); - TrustIdInfo autocryptPeerInfo = TrustIdInfo.create(associatedUserIdInfo, autocryptPeer, packageName, drawable, autocryptPeerIntent); + AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo + .create(associatedUserIdInfo, autocryptPeer, packageName, drawable, autocryptPeerIntent); identities.set(position, autocryptPeerInfo); } else { - TrustIdInfo autocryptPeerInfo = TrustIdInfo.create(autocryptPeer, packageName, drawable, autocryptPeerIntent); + AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo + .create(autocryptPeer, packageName, drawable, autocryptPeerIntent); identities.add(autocryptPeerInfo); } } @@ -150,7 +151,7 @@ public class IdentityLoader extends AsyncTaskLoader> { } } - private Intent getTrustIdActivityIntentIfResolvable(String packageName, String autocryptPeer) { + private Intent getAutocryptPeerActivityIntentIfResolvable(String packageName, String autocryptPeer) { Intent intent = new Intent(); intent.setAction("org.autocrypt.PEER_ACTION"); intent.setPackage(packageName); @@ -165,7 +166,7 @@ public class IdentityLoader extends AsyncTaskLoader> { } } - private static UserIdInfo findUserIdMatchingTrustId(List identities, String autocryptPeer) { + private static UserIdInfo findUserIdMatchingAutocryptPeer(List identities, String autocryptPeer) { for (IdentityInfo identityInfo : identities) { if (identityInfo instanceof UserIdInfo) { UserIdInfo userIdInfo = (UserIdInfo) identityInfo; @@ -304,28 +305,28 @@ public class IdentityLoader extends AsyncTaskLoader> { } @AutoValue - public abstract static class TrustIdInfo implements IdentityInfo { + public abstract static class AutocryptPeerInfo implements IdentityInfo { public abstract int getRank(); public abstract int getVerified(); public abstract boolean isPrimary(); - public abstract String getTrustId(); + public abstract String getIdentity(); public abstract String getPackageName(); @Nullable public abstract Drawable getAppIcon(); @Nullable public abstract UserIdInfo getUserIdInfo(); @Nullable - public abstract Intent getTrustIdIntent(); + public abstract Intent getAutocryptPeerIntent(); - static TrustIdInfo create(UserIdInfo userIdInfo, String autocryptPeer, String packageName, + static AutocryptPeerInfo create(UserIdInfo userIdInfo, String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityLoader_TrustIdInfo(userIdInfo.getRank(), userIdInfo.getVerified(), + return new AutoValue_IdentityLoader_AutocryptPeerInfo(userIdInfo.getRank(), userIdInfo.getVerified(), userIdInfo.isPrimary(), autocryptPeer, packageName, appIcon, userIdInfo, autocryptPeerIntent); } - static TrustIdInfo create(String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityLoader_TrustIdInfo( + static AutocryptPeerInfo create(String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { + return new AutoValue_IdentityLoader_AutocryptPeerInfo( 0, Certs.VERIFIED_SELF, false, autocryptPeer, packageName, appIcon, null, autocryptPeerIntent); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java index d606eed4a..499be251c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java @@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.ui.keyview.LinkedIdViewFragment; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.LinkedIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.TrustIdInfo; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.AutocryptPeerInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.UserIdInfo; import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.util.Preferences; @@ -129,8 +129,8 @@ public class IdentitiesPresenter implements LoaderCallbacks> showLinkedId((LinkedIdInfo) info); } else if (info instanceof UserIdInfo) { showUserIdInfo((UserIdInfo) info); - } else if (info instanceof TrustIdInfo) { - Intent autocryptPeerIntent = ((TrustIdInfo) info).getTrustIdIntent(); + } else if (info instanceof AutocryptPeerInfo) { + Intent autocryptPeerIntent = ((AutocryptPeerInfo) info).getAutocryptPeerIntent(); if (autocryptPeerIntent != null) { viewKeyMvpView.startActivity(autocryptPeerIntent); } @@ -176,7 +176,7 @@ public class IdentitiesPresenter implements LoaderCallbacks> } public void onClickForgetIdentity(int position) { - TrustIdInfo info = (TrustIdInfo) identitiesAdapter.getInfo(position); + AutocryptPeerInfo info = (AutocryptPeerInfo) identitiesAdapter.getInfo(position); if (info == null) { Timber.e("got a 'forget' click on a bad trust id"); return; @@ -184,7 +184,7 @@ public class IdentitiesPresenter implements LoaderCallbacks> AutocryptPeerDataAccessObject autocryptPeerDao = new AutocryptPeerDataAccessObject(context, info.getPackageName()); - autocryptPeerDao.delete(info.getTrustId()); + autocryptPeerDao.delete(info.getIdentity()); } public interface IdentitiesMvpView { From ebe262015a53bbe8ecb6abf9093b66f314ba2272 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 15 Jan 2018 18:16:48 +0100 Subject: [PATCH 07/14] Change Autocrypt logic to more closely match the spec --- .../operations/results/SaveKeyringResult.java | 8 +- .../AutocryptPeerDataAccessObject.java | 17 +++ .../keychain/provider/KeychainProvider.java | 35 ++--- .../keychain/remote/AutocryptInteractor.java | 136 +++++++++++------- .../keychain/remote/OpenPgpService.java | 8 +- 5 files changed, 118 insertions(+), 86 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SaveKeyringResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SaveKeyringResult.java index bdb1ce66d..8b41a52f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SaveKeyringResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SaveKeyringResult.java @@ -24,12 +24,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; public class SaveKeyringResult extends OperationResult { - public final long mRingMasterKeyId; + public final Long savedMasterKeyId; public SaveKeyringResult(int result, OperationLog log, CanonicalizedKeyRing ring) { super(result, log); - mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; + savedMasterKeyId = ring != null ? ring.getMasterKeyId() : null; } // Some old key was updated @@ -46,13 +46,13 @@ public class SaveKeyringResult extends OperationResult { public SaveKeyringResult(Parcel source) { super(source); - mRingMasterKeyId = source.readLong(); + savedMasterKeyId = source.readLong(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeLong(mRingMasterKeyId); + dest.writeLong(savedMasterKeyId); } public static Creator CREATOR = new Creator() { 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 76895496b..7fa579894 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java @@ -153,6 +153,23 @@ public class AutocryptPeerDataAccessObject { return null; } + public Date getLastSeenGossip(String autocryptId) { + Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), + null, null, null, null); + + try { + if (cursor != null && cursor.moveToFirst()) { + long lastUpdated = cursor.getColumnIndex(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY); + return new Date(lastUpdated); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + public void updateLastSeen(String autocryptId, Date date) { ContentValues cv = new ContentValues(); cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime()); 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 d8d83eef3..d303eb0ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -1114,42 +1114,25 @@ public class KeychainProvider extends ContentProvider { break; } case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { - ContentValues actualValues = new ContentValues(); String packageName = uri.getPathSegments().get(2); - actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); - actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment()); + String identifier = uri.getLastPathSegment(); + values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); + values.put(ApiAutocryptPeer.IDENTIFIER, identifier); - Long newLastSeen = values.getAsLong(ApiAutocryptPeer.LAST_SEEN); - if (newLastSeen != null) { - actualValues.put(ApiAutocryptPeer.LAST_SEEN, newLastSeen); - db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); - break; + int updated = db.update(Tables.API_AUTOCRYPT_PEERS, values, + ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?", + new String[] { packageName, identifier }); + if (updated == 0) { + db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values); } Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID); if (masterKeyId != null) { - Long lastSeenKey = values.getAsLong(ApiAutocryptPeer.LAST_SEEN_KEY); - Long preferEncryptState = values.getAsLong(ApiAutocryptPeer.IS_MUTUAL); - - actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); - actualValues.put(ApiAutocryptPeer.LAST_SEEN_KEY, lastSeenKey); - actualValues.put(ApiAutocryptPeer.IS_MUTUAL, preferEncryptState); - - db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null); - break; } - Long gossipMasterKeyId = values.getAsLong(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID); - if (gossipMasterKeyId != null) { - Long gossipLastSeenKey = values.getAsLong(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY); - - actualValues.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, gossipMasterKeyId); - actualValues.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, gossipLastSeenKey); - - db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); + if (gossipMasterKeyId != null && !gossipMasterKeyId.equals(masterKeyId)) { contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(gossipMasterKeyId), null); - break; } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index bfdea532e..0d56522f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -5,15 +5,17 @@ import java.io.IOException; import java.util.Date; import android.content.Context; +import android.support.annotation.Nullable; import org.openintents.openpgp.AutocryptPeerUpdate; import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.util.Log; +import timber.log.Timber; class AutocryptInteractor { @@ -33,74 +35,100 @@ class AutocryptInteractor { this.keyWritableRepository = keyWritableRepository; } - void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) - throws PgpGeneralException, IOException { - if (autocryptPeerUpdate == null) { - return; - } - - Long newMasterKeyId; - if (autocryptPeerUpdate.hasKeyData()) { - UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData()); - if (uncachedKeyRing.isSecret()) { - Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring"); - return; - } - // this will merge if the key already exists - no worries! - keyWritableRepository.savePublicKeyRing(uncachedKeyRing); - newMasterKeyId = uncachedKeyRing.getMasterKeyId(); - } else { - newMasterKeyId = null; - } - + void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) { Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); - autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate); - if (newMasterKeyId == null) { + // 1. If the message’s effective date is older than the peers[from-addr].autocrypt_timestamp value, then no changes are required, and the update process terminates. + Date lastSeenAutocrypt = autocryptPeerDao.getLastSeenKey(autocryptPeerId); + if (lastSeenAutocrypt != null && lastSeenAutocrypt.after(effectiveDate)) { return; } - Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); - if (lastSeenKey == null || effectiveDate.after(lastSeenKey)) { - boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL; - autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual); + // 2. If the message’s effective date is more recent than peers[from-addr].last_seen then set peers[from-addr].last_seen to the message’s effective date. + Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId); + if (lastSeen == null || lastSeen.after(effectiveDate)) { + autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate); } + + // 3. If the Autocrypt header is unavailable, no further changes are required and the update process terminates. + if (!autocryptPeerUpdate.hasKeyData()) { + return; + } + + SaveKeyringResult saveKeyringResult = parseAndImportAutocryptKeyData(autocryptPeerUpdate); + if (saveKeyringResult == null) { + return; + } + + // 4. Set peers[from-addr].autocrypt_timestamp to the message’s effective date. + // 5. Set peers[from-addr].public_key to the corresponding keydata value of the Autocrypt header. + Long newMasterKeyId = saveKeyringResult.savedMasterKeyId; + // 6. Set peers[from-addr].prefer_encrypt to the corresponding prefer-encrypt value of the Autocrypt header. + boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL; + + autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual); } - - void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) - throws PgpGeneralException, IOException { - if (autocryptPeerUpdate == null) { - return; - } - - Long newMasterKeyId; - if (autocryptPeerUpdate.hasKeyData()) { - UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData()); - if (uncachedKeyRing.isSecret()) { - Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring"); - return; - } - // this will merge if the key already exists - no worries! - keyWritableRepository.savePublicKeyRing(uncachedKeyRing); - newMasterKeyId = uncachedKeyRing.getMasterKeyId(); - } else { - newMasterKeyId = null; - } - - Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId); + void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) { Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); - if (newMasterKeyId == null) { + + // 1. If gossip-addr does not match any recipient in the mail’s To or Cc header, the update process terminates (i.e., header is ignored). + // -> This should be taken care of in the mail client that sends us this data! + + // 2. If peers[gossip-addr].gossip_timestamp is more recent than the message’s effective date, then the update process terminates. + Date lastSeenGossip = autocryptPeerDao.getLastSeenGossip(autocryptPeerId); + if (lastSeenGossip != null && lastSeenGossip.after(effectiveDate)) { return; } - Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); - if (lastSeenKey != null && effectiveDate.before(lastSeenKey)) { + if (!autocryptPeerUpdate.hasKeyData()) { return; } - if (lastSeen == null || effectiveDate.after(lastSeen)) { - autocryptPeerDao.updateKeyGossip(autocryptPeerId, effectiveDate, newMasterKeyId); + SaveKeyringResult saveKeyringResult = parseAndImportAutocryptKeyData(autocryptPeerUpdate); + if (saveKeyringResult == null) { + return; } + + // 3. Set peers[gossip-addr].gossip_timestamp to the message’s effective date. + // 4. Set peers[gossip-addr].gossip_key to the value of the keydata attribute. + Long newMasterKeyId = saveKeyringResult.savedMasterKeyId; + + autocryptPeerDao.updateKeyGossip(autocryptPeerId, effectiveDate, newMasterKeyId); + } + + @Nullable + private SaveKeyringResult parseAndImportAutocryptKeyData(AutocryptPeerUpdate autocryptPeerUpdate) { + UncachedKeyRing uncachedKeyRing = parseAutocryptKeyData(autocryptPeerUpdate); + if (uncachedKeyRing != null) { + return importAutocryptKeyData(uncachedKeyRing); + } + return null; + } + + @Nullable + private SaveKeyringResult importAutocryptKeyData(UncachedKeyRing uncachedKeyRing) { + SaveKeyringResult saveKeyringResult = keyWritableRepository.savePublicKeyRing(uncachedKeyRing); + if (!saveKeyringResult.success()) { + Timber.e(Constants.TAG, "Error inserting key - ignoring!"); + return null; + } + return saveKeyringResult; + } + + @Nullable + private UncachedKeyRing parseAutocryptKeyData(AutocryptPeerUpdate autocryptPeerUpdate) { + UncachedKeyRing uncachedKeyRing; + try { + uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData()); + } catch (IOException | PgpGeneralException e) { + Timber.e(Constants.TAG, "Error parsing public key! - Ignoring"); + return null; + } + if (uncachedKeyRing.isSecret()) { + Timber.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring"); + return null; + } + return uncachedKeyRing; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 75c52a1da..9550f161b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -762,7 +762,9 @@ public class OpenPgpService extends Service { String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE); - autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate); + if (autocryptPeerUpdate != null) { + autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate); + } } if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) { @@ -770,7 +772,9 @@ public class OpenPgpService extends Service { for (String address : updates.keySet()) { Timber.d(Constants.TAG, "Updating gossip state: " + address); AutocryptPeerUpdate update = updates.getParcelable(address); - autocryptInteractor.updateAutocryptPeerGossipState(address, update); + if (update != null) { + autocryptInteractor.updateAutocryptPeerGossipState(address, update); + } } } From 31b23d2085257624562b27ecbc3d1afcdade9da6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 16 Jan 2018 19:38:27 +0100 Subject: [PATCH 08/14] Small optimizations for display of package icons in key list --- .../keychain/provider/KeychainProvider.java | 2 +- .../keychain/ui/adapter/KeySectionedListAdapter.java | 4 ++-- .../keychain/ui/util/adapter/CursorAdapter.java | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) 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 d303eb0ca..d7f7f2efe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -366,7 +366,7 @@ public class KeychainProvider extends ContentProvider { "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY + " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); projectionMap.put(KeyRings.API_KNOWN_TO_PACKAGE_NAMES, - "GROUP_CONCAT(aTI." + ApiAutocryptPeer.PACKAGE_NAME + ") AS " + "GROUP_CONCAT(DISTINCT aTI." + ApiAutocryptPeer.PACKAGE_NAME + ") AS " + KeyRings.API_KNOWN_TO_PACKAGE_NAMES); qb.setProjectionMap(projectionMap); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java index c4ae0bf3a..39de7a777 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java @@ -494,7 +494,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter packageNames = keyItem.getTrustIdPackages(); + List packageNames = keyItem.getAutocryptPeerIdPackages(); if (!keyItem.isSecret() && !packageNames.isEmpty()) { String packageName = packageNames.get(0); @@ -627,7 +627,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter 0; } - public List getTrustIdPackages() { + public List getAutocryptPeerIdPackages() { int index = getColumnIndexOrThrow(KeyRings.API_KNOWN_TO_PACKAGE_NAMES); String packageNames = getString(index); if (packageNames == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java index 29a1f32b4..562f83383 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java @@ -392,8 +392,7 @@ public abstract class CursorAdapter Date: Wed, 17 Jan 2018 14:41:10 +0100 Subject: [PATCH 09/14] Further small optimizations to Autocrypt logic --- .../AutocryptPeerDataAccessObject.java | 159 +++++++++--------- .../remote/KeychainExternalProvider.java | 45 ++--- 2 files changed, 102 insertions(+), 102 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 7fa579894..dd6f3d05e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java @@ -18,15 +18,16 @@ package org.sufficientlysecure.keychain.provider; +import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.support.annotation.Nullable; import android.text.format.DateUtils; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; @@ -198,85 +199,14 @@ public class AutocryptPeerDataAccessObject { queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null); } - 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. + public List determineAutocryptRecommendations(String... autocryptIds) { + List result = new ArrayList<>(autocryptIds.length); -For the purposes of the rest of this recommendation, if either public_key or gossip_key is revoked, expired, or otherwise known to be unusable for encryption, then treat that key as though it were null (not present). - -If both public_key and gossip_key are null, then set ui-recommendation to disable and terminate. - -Otherwise, we derive the recommendation using a two-phase algorithm. The first phase computes the preliminary-recommendation. - -Preliminary Recommendation -If public_key is null, then set target-keys[to-addr] to gossip_key and set preliminary-recommendation to discourage and skip to the Deciding to Encrypt by Default. - -Otherwise, set target-keys[to-addr] to public_key. - -If autocrypt_timestamp is more than 35 days older than last_seen, set preliminary-recommendation to discourage. - -Otherwise, set preliminary-recommendation to available. - */ - - 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); + Cursor cursor = queryAutocryptPeerData(autocryptIds); try { 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)) { - 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 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; - } - - AutocryptPeerStateResult peerResult = new AutocryptPeerStateResult( - AutocryptState.DISABLE, null, false); - result.put(autocryptId, peerResult); + AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(cursor); + result.add(peerResult); } } finally { cursor.close(); @@ -285,12 +215,81 @@ Otherwise, set preliminary-recommendation to available. return result; } - public static class AutocryptPeerStateResult { + /** Determines Autocrypt "ui-recommendation", according to spec. + * See https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages + */ + private AutocryptRecommendationResult determineAutocryptRecommendation(Cursor cursor) { + String peerId = cursor.getString(INDEX_IDENTIFIER); + + AutocryptRecommendationResult keyRecommendation = determineAutocryptKeyRecommendation(peerId, cursor); + if (keyRecommendation != null) return keyRecommendation; + + AutocryptRecommendationResult gossipRecommendation = determineAutocryptGossipRecommendation(peerId, cursor); + if (gossipRecommendation != null) return gossipRecommendation; + + return new AutocryptRecommendationResult(peerId, AutocryptState.DISABLE, null, false); + } + + @Nullable + private AutocryptRecommendationResult determineAutocryptKeyRecommendation(String peerId, Cursor cursor) { + 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) { + return null; + } + + 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 AutocryptRecommendationResult(peerId, AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified); + } + + boolean isMutual = cursor.getInt(INDEX_STATE) != 0; + if (isMutual) { + return new AutocryptRecommendationResult(peerId, AutocryptState.MUTUAL, masterKeyId, isVerified); + } else { + return new AutocryptRecommendationResult(peerId, AutocryptState.AVAILABLE, masterKeyId, isVerified); + } + } + + @Nullable + private AutocryptRecommendationResult determineAutocryptGossipRecommendation(String peerId, Cursor cursor) { + 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) { + return null; + } + + long masterKeyId = cursor.getLong(INDEX_GOSSIP_MASTER_KEY_ID); + return new AutocryptRecommendationResult(peerId, AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified); + } + + private Cursor queryAutocryptPeerData(String[] autocryptIds) { + StringBuilder selection = new StringBuilder(ApiAutocryptPeer.IDENTIFIER + " IN (?"); + for (int i = 1; i < autocryptIds.length; i++) { + selection.append(",?"); + } + selection.append(")"); + + return queryInterface.query(ApiAutocryptPeer.buildByPackageName(packageName), + PROJECTION_AUTOCRYPT_QUERY, selection.toString(), autocryptIds, null); + } + + public static class AutocryptRecommendationResult { + public final String peerId; public final Long masterKeyId; public final AutocryptState autocryptState; public final boolean isVerified; - AutocryptPeerStateResult(AutocryptState autocryptState, Long masterKeyId, boolean isVerified) { + AutocryptRecommendationResult(String peerId, AutocryptState autocryptState, Long masterKeyId, + boolean isVerified) { + this.peerId = peerId; this.autocryptState = autocryptState; this.masterKeyId = masterKeyId; this.isVerified = isVerified; 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 d0b17bb0f..6bd7179da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -22,7 +22,6 @@ 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; @@ -41,7 +40,7 @@ import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptPeerStateResult; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; @@ -249,8 +248,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC AutocryptStatus.UID_ADDRESS + " TEXT, " + AutocryptStatus.UID_MASTER_KEY_ID + " INT, " + AutocryptStatus.UID_CANDIDATES + " INT, " + + AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT DEFAULT " + AutocryptStatus.AUTOCRYPT_PEER_DISABLED + ", " + AutocryptStatus.AUTOCRYPT_KEY_STATUS + " INT, " + - AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT, " + AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + " INT" + ");"); ContentValues cv = new ContentValues(); @@ -278,7 +277,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!"); } if (!isWildcardSelector && queriesAutocryptResult) { - fillTempTableWithAutocryptResult(selectionArgs, db, callingPackageName, cv); + AutocryptPeerDataAccessObject autocryptPeerDao = + new AutocryptPeerDataAccessObject(this, callingPackageName); + fillTempTableWithAutocryptRecommendations(db, autocryptPeerDao, selectionArgs); } qb.setTables(TEMP_TABLE_QUERIED_ADDRESSES); @@ -338,30 +339,30 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC return cursor; } - private void fillTempTableWithAutocryptResult(String[] selectionArgs, SQLiteDatabase db, String callingPackageName, - ContentValues cv) { - AutocryptPeerDataAccessObject autocryptPeerDao = - new AutocryptPeerDataAccessObject(this, callingPackageName); - Map autocryptStates = autocryptPeerDao.getAutocryptState(selectionArgs); + private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db, + AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) { + List autocryptStates = + autocryptPeerDao.determineAutocryptRecommendations(peerIds); - for (String autocryptId : selectionArgs) { + fillTempTableWithAutocryptRecommendations(db, autocryptStates); + } + + private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db, + List autocryptRecommendations) { + ContentValues cv = new ContentValues(); + for (AutocryptRecommendationResult peerResult : autocryptRecommendations) { 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); - } + 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 }); + new String[] { peerResult.peerId }); } } From e2f89f8d8d5476bbfdfe81ac8a76a2ec871e84a5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jan 2018 15:09:33 +0100 Subject: [PATCH 10/14] Use Autocrypt gossip field for deduplication --- .../remote/ui/dialog/RemoteDeduplicatePresenter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index 8e2fc55ae..354053686 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.remote.ui.dialog; +import java.util.Date; import java.util.List; import android.content.Context; @@ -120,8 +121,8 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { return; } - // long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); - // autocryptPeerDao.updateToSelectedState(duplicateAddress, masterKeyId); + long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); + autocryptPeerDao.updateKeyGossip(duplicateAddress, new Date(), masterKeyId); view.finish(); } From f5d919595803ef7146d981034332c21c59485486 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jan 2018 15:45:33 +0100 Subject: [PATCH 11/14] Rename database fields for consistency --- .../sufficientlysecure/keychain/provider/KeychainContract.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7062d6179..65fc34cfd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -102,7 +102,7 @@ public class KeychainContract { interface ApiAutocryptPeerColumns { String PACKAGE_NAME = "package_name"; String IDENTIFIER = "identifier"; - String LAST_SEEN = "last_updated"; + String LAST_SEEN = "last_seen"; String MASTER_KEY_ID = "master_key_id"; String LAST_SEEN_KEY = "last_seen_key"; From 53953fb058b080c6403545823eee7fae70649444 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 17 Jan 2018 15:40:46 +0100 Subject: [PATCH 12/14] Add migration for autocrypt peers --- .../keychain/provider/KeychainDatabase.java | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) 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 f3b140fe1..c53183901 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -412,10 +412,38 @@ 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);"); + db.beginTransaction(); + db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO tmp"); + db.execSQL("CREATE TABLE api_autocrypt_peers (" + + "package_name TEXT NOT NULL, " + + "identifier TEXT NOT NULL, " + + "last_seen INTEGER, " + + "last_seen_key INTEGER NULL, " + + "is_mutual INTEGER NULL, " + + "master_key_id INTEGER NULL, " + + "gossip_master_key_id INTEGER NULL, " + + "gossip_last_seen_key INTEGER NULL, " + + "PRIMARY KEY(package_name, identifier), " + + "FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE" + + ")"); + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, is_mutual) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + + "FROM tmp WHERE state IN (1,2)"); + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + + "FROM tmp WHERE state = 3"); + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 1 " + + "FROM tmp WHERE state = 4"); + db.execSQL("DROP TABLE tmp"); + db.setTransactionSuccessful(); + db.endTransaction(); + db.execSQL("DROP INDEX keys_by_rank"); + db.execSQL("CREATE INDEX uids_by_email ON user_packets (email);"); db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);"); } } From ca8835420dabe82d4b320ee723196773bbe421fc Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 20 Jan 2018 22:08:51 +0100 Subject: [PATCH 13/14] Remember "origin" of gossip keys --- .../AutocryptPeerDataAccessObject.java | 15 ++++++++++- .../keychain/provider/KeychainContract.java | 5 ++++ .../keychain/provider/KeychainDatabase.java | 25 ++++++++++++------- .../keychain/provider/KeychainProvider.java | 1 + .../keychain/remote/AutocryptInteractor.java | 2 +- .../keychain/remote/OpenPgpService.java | 13 +++++----- .../ui/dialog/RemoteDeduplicatePresenter.java | 2 +- .../remote/KeychainExternalProviderTest.java | 4 +-- 8 files changed, 46 insertions(+), 21 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 dd6f3d05e..62afe2bec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java @@ -187,10 +187,23 @@ public class AutocryptPeerDataAccessObject { .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); } - public void updateKeyGossip(String autocryptId, Date effectiveDate, long masterKeyId) { + public void updateKeyGossipFromAutocrypt(String autocryptId, Date effectiveDate, long masterKeyId) { + updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_AUTOCRYPT); + } + + public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) { + updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_SIGNATURE); + } + + public void updateKeyGossipFromDedup(String autocryptId, Date effectiveDate, long masterKeyId) { + updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_DEDUP); + } + + private void updateKeyGossip(String autocryptId, Date effectiveDate, long masterKeyId, int origin) { ContentValues cv = new ContentValues(); cv.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, masterKeyId); cv.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, effectiveDate.getTime()); + cv.put(ApiAutocryptPeer.GOSSIP_ORIGIN, origin); queryInterface .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); } 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 65fc34cfd..b97bb44cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -110,6 +110,7 @@ public class KeychainContract { String GOSSIP_MASTER_KEY_ID = "gossip_master_key_id"; String GOSSIP_LAST_SEEN_KEY = "gossip_last_seen_key"; + String GOSSIP_ORIGIN = "gossip_origin"; } public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY; @@ -382,6 +383,10 @@ public class KeychainContract { public static final String GOSSIP_KEY_IS_EXPIRED = "gossip_key_is_expired"; public static final String GOSSIP_KEY_IS_VERIFIED = "gossip_key_is_verified"; + public static final int GOSSIP_ORIGIN_AUTOCRYPT = 0; + public static final int GOSSIP_ORIGIN_SIGNATURE = 10; + public static final int GOSSIP_ORIGIN_DEDUP = 20; + public static Uri buildByKeyUri(Uri uri) { return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).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 c53183901..b1ed2ae82 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -179,6 +179,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NULL, " + ApiAutocryptPeerColumns.GOSSIP_MASTER_KEY_ID + " INTEGER NULL, " + ApiAutocryptPeerColumns.GOSSIP_LAST_SEEN_KEY + " INTEGER NULL, " + + ApiAutocryptPeerColumns.GOSSIP_ORIGIN + " INTEGER NULL, " + "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", " + ApiAutocryptPeerColumns.IDENTIFIER + "), " + "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES " @@ -418,26 +419,32 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "package_name TEXT NOT NULL, " + "identifier TEXT NOT NULL, " + "last_seen INTEGER, " - + "last_seen_key INTEGER NULL, " - + "is_mutual INTEGER NULL, " - + "master_key_id INTEGER NULL, " - + "gossip_master_key_id INTEGER NULL, " - + "gossip_last_seen_key INTEGER NULL, " + + "last_seen_key INTEGER, " + + "is_mutual INTEGER, " + + "master_key_id INTEGER, " + + "gossip_master_key_id INTEGER, " + + "gossip_last_seen_key INTEGER, " + + "gossip_origin INTEGER, " + "PRIMARY KEY(package_name, identifier), " + "FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE" + ")"); + // Note: Keys from Autocrypt 0.X with state == "reset" (0) are dropped db.execSQL("INSERT INTO api_autocrypt_peers " + - "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, is_mutual) " + + "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + - "FROM tmp WHERE state IN (1,2)"); + "FROM tmp WHERE state = 1"); // Autocrypt 0.X, "gossip" -> now origin=autocrypt + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 20 " + + "FROM tmp WHERE state = 2"); // "selected" keys -> now origin=dedup db.execSQL("INSERT INTO api_autocrypt_peers " + "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + - "FROM tmp WHERE state = 3"); + "FROM tmp WHERE state = 3"); // Autocrypt 0.X, state = "available" db.execSQL("INSERT INTO api_autocrypt_peers " + "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 1 " + - "FROM tmp WHERE state = 4"); + "FROM tmp WHERE state = 4"); // from Autocrypt 0.X, state = "mutual" db.execSQL("DROP TABLE tmp"); db.setTransactionSuccessful(); db.endTransaction(); 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 d7f7f2efe..bd5881eda 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -729,6 +729,7 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(ApiAutocryptPeer.LAST_SEEN_KEY, ApiAutocryptPeer.LAST_SEEN_KEY); projectionMap.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID); projectionMap.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY); + projectionMap.put(ApiAutocryptPeer.GOSSIP_ORIGIN, ApiAutocryptPeer.GOSSIP_ORIGIN); projectionMap.put(ApiAutocryptPeer.KEY_IS_REVOKED, "ac_key." + Keys.IS_REVOKED + " AS " + ApiAutocryptPeer.KEY_IS_REVOKED); projectionMap.put(ApiAutocryptPeer.KEY_IS_EXPIRED, "(CASE" + " WHEN ac_key." + Keys.EXPIRY + " IS NULL THEN 0" + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index 0d56522f3..3026aaaba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -94,7 +94,7 @@ class AutocryptInteractor { // 4. Set peers[gossip-addr].gossip_key to the value of the keydata attribute. Long newMasterKeyId = saveKeyringResult.savedMasterKeyId; - autocryptPeerDao.updateKeyGossip(autocryptPeerId, effectiveDate, newMasterKeyId); + autocryptPeerDao.updateKeyGossipFromAutocrypt(autocryptPeerId, effectiveDate, newMasterKeyId); } @Nullable diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 9550f161b..1fcba550f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -577,13 +577,12 @@ public class OpenPgpService extends Service { long masterKeyId = signatureResult.getKeyId(); if (autocryptPeerMasterKeyId == null) { - // TODO -// Date now = new Date(); -// Date effectiveTime = signatureResult.getSignatureTimestamp(); -// if (effectiveTime.after(now)) { -// effectiveTime = now; -// } -// autocryptPeerentityDao.updateKeyGossip(autocryptPeerId, effectiveTime, masterKeyId); + Date now = new Date(); + Date effectiveTime = signatureResult.getSignatureTimestamp(); + if (effectiveTime.after(now)) { + effectiveTime = now; + } + autocryptPeerentityDao.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId); return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW); } else if (masterKeyId == autocryptPeerMasterKeyId) { return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index 354053686..4717f7da5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -122,7 +122,7 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { } long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); - autocryptPeerDao.updateKeyGossip(duplicateAddress, new Date(), masterKeyId); + autocryptPeerDao.updateKeyGossipFromDedup(duplicateAddress, new Date(), masterKeyId); view.finish(); } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java index 950d3f275..e27a97ac9 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java @@ -305,7 +305,7 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKeyGossip("tid", new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.updateKeyGossipFromAutocrypt("tid", new Date(), KEY_ID_PUBLIC); autocryptPeerDao.delete("tid"); Cursor cursor = contentResolver.query( @@ -330,7 +330,7 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKeyGossip(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.updateKeyGossipFromAutocrypt(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC); certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); Cursor cursor = contentResolver.query( From eb241a30d79c46de8459ec4c17ce42a8145356b5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Jan 2018 11:20:23 +0100 Subject: [PATCH 14/14] Improve migration mechanism --- .../keychain/provider/KeychainDatabase.java | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) 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 b1ed2ae82..8350b9da1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -24,6 +24,7 @@ import java.io.FileOutputStream; import java.io.IOException; import android.content.Context; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; @@ -413,44 +414,47 @@ public class KeychainDatabase extends SQLiteOpenHelper { + ")"); case 24: - db.beginTransaction(); - db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO tmp"); - db.execSQL("CREATE TABLE api_autocrypt_peers (" - + "package_name TEXT NOT NULL, " - + "identifier TEXT NOT NULL, " - + "last_seen INTEGER, " - + "last_seen_key INTEGER, " - + "is_mutual INTEGER, " - + "master_key_id INTEGER, " - + "gossip_master_key_id INTEGER, " - + "gossip_last_seen_key INTEGER, " - + "gossip_origin INTEGER, " - + "PRIMARY KEY(package_name, identifier), " - + "FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE" - + ")"); - // Note: Keys from Autocrypt 0.X with state == "reset" (0) are dropped - db.execSQL("INSERT INTO api_autocrypt_peers " + - "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " + - "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + - "FROM tmp WHERE state = 1"); // Autocrypt 0.X, "gossip" -> now origin=autocrypt - db.execSQL("INSERT INTO api_autocrypt_peers " + - "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " + - "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 20 " + - "FROM tmp WHERE state = 2"); // "selected" keys -> now origin=dedup - db.execSQL("INSERT INTO api_autocrypt_peers " + - "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + - "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + + try { + db.beginTransaction(); + db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO tmp"); + db.execSQL("CREATE TABLE api_autocrypt_peers (" + + "package_name TEXT NOT NULL, " + + "identifier TEXT NOT NULL, " + + "last_seen INTEGER, " + + "last_seen_key INTEGER, " + + "is_mutual INTEGER, " + + "master_key_id INTEGER, " + + "gossip_master_key_id INTEGER, " + + "gossip_last_seen_key INTEGER, " + + "gossip_origin INTEGER, " + + "PRIMARY KEY(package_name, identifier), " + + "FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE" + + ")"); + // Note: Keys from Autocrypt 0.X with state == "reset" (0) are dropped + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + + "FROM tmp WHERE state = 1"); // Autocrypt 0.X, "gossip" -> now origin=autocrypt + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 20 " + + "FROM tmp WHERE state = 2"); // "selected" keys -> now origin=dedup + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " + "FROM tmp WHERE state = 3"); // Autocrypt 0.X, state = "available" - db.execSQL("INSERT INTO api_autocrypt_peers " + - "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + - "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 1 " + + db.execSQL("INSERT INTO api_autocrypt_peers " + + "(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " + + "SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 1 " + "FROM tmp WHERE state = 4"); // from Autocrypt 0.X, state = "mutual" - db.execSQL("DROP TABLE tmp"); - db.setTransactionSuccessful(); - db.endTransaction(); + db.execSQL("DROP TABLE tmp"); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + db.execSQL("CREATE INDEX IF NOT EXISTS uids_by_email ON user_packets (email);"); db.execSQL("DROP INDEX keys_by_rank"); - db.execSQL("CREATE INDEX uids_by_email ON user_packets (email);"); db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);"); } }