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()); }