Merge pull request #2253 from open-keychain/autocrypt-1.0
Autocrypt 1.0
This commit is contained in:
@@ -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<SaveKeyringResult> CREATOR = new Creator<SaveKeyringResult>() {
|
||||
|
||||
@@ -18,19 +18,54 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
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;
|
||||
|
||||
|
||||
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.IDENTIFIER,
|
||||
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_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;
|
||||
|
||||
|
||||
@@ -38,7 +73,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 +98,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 +121,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 +138,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 +154,163 @@ public class AutocryptPeerDataAccessObject {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updateToResetState(String autocryptId, Date effectiveDate) {
|
||||
updateAutocryptState(autocryptId, effectiveDate, null, ApiAutocryptPeer.RESET);
|
||||
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 updateToSelectedState(String autocryptId, long masterKeyId) {
|
||||
updateAutocryptState(autocryptId, new Date(), masterKeyId, ApiAutocryptPeer.SELECTED);
|
||||
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 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 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);
|
||||
}
|
||||
|
||||
public void delete(String autocryptId) {
|
||||
mQueryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null);
|
||||
queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null);
|
||||
}
|
||||
|
||||
public List<AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
|
||||
List<AutocryptRecommendationResult> result = new ArrayList<>(autocryptIds.length);
|
||||
|
||||
Cursor cursor = queryAutocryptPeerData(autocryptIds);
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(cursor);
|
||||
result.add(peerResult);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
AutocryptRecommendationResult(String peerId, AutocryptState autocryptState, Long masterKeyId,
|
||||
boolean isVerified) {
|
||||
this.peerId = peerId;
|
||||
this.autocryptState = autocryptState;
|
||||
this.masterKeyId = masterKeyId;
|
||||
this.isVerified = isVerified;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum AutocryptState {
|
||||
DISABLE, DISCOURAGED_OLD, DISCOURAGED_GOSSIP, AVAILABLE, MUTUAL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +102,15 @@ public class KeychainContract {
|
||||
interface ApiAutocryptPeerColumns {
|
||||
String PACKAGE_NAME = "package_name";
|
||||
String IDENTIFIER = "identifier";
|
||||
String LAST_SEEN = "last_updated";
|
||||
String LAST_SEEN_KEY = "last_seen_key";
|
||||
String STATE = "state";
|
||||
String LAST_SEEN = "last_seen";
|
||||
|
||||
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";
|
||||
String GOSSIP_ORIGIN = "gossip_origin";
|
||||
}
|
||||
|
||||
public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
|
||||
@@ -371,17 +376,25 @@ 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 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 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 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -54,7 +55,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 +174,13 @@ 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, "
|
||||
+ ApiAutocryptPeerColumns.GOSSIP_ORIGIN + " INTEGER NULL, "
|
||||
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
|
||||
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "
|
||||
+ "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES "
|
||||
@@ -229,11 +233,13 @@ 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 ("
|
||||
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
|
||||
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
|
||||
+ UserPacketsColumns.EMAIL + ");");
|
||||
|
||||
Preferences.getPreferences(mContext).setKeySignaturesTableInitialized();
|
||||
}
|
||||
@@ -406,6 +412,50 @@ 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:
|
||||
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 " +
|
||||
"FROM tmp WHERE state = 4"); // from Autocrypt 0.X, state = "mutual"
|
||||
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 keys_by_rank ON keys(rank, master_key_id);");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -717,30 +717,61 @@ 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<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(ApiAutocryptPeer._ID, "oid AS " + ApiAutocryptPeer._ID);
|
||||
projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME);
|
||||
projectionMap.put(ApiAutocryptPeer._ID, Tables.API_AUTOCRYPT_PEERS + ".oid AS " + ApiAutocryptPeer._ID);
|
||||
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.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" +
|
||||
" 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 +
|
||||
" 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 +
|
||||
" 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);
|
||||
|
||||
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);
|
||||
@@ -1084,34 +1115,27 @@ 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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
if (values.containsKey(ApiAutocryptPeer.LAST_SEEN_KEY)) {
|
||||
actualValues.put(ApiAutocryptPeer.LAST_SEEN_KEY,
|
||||
values.getAsLong(ApiAutocryptPeer.LAST_SEEN_KEY));
|
||||
Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID);
|
||||
if (masterKeyId != null) {
|
||||
contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null);
|
||||
}
|
||||
if (values.containsKey(ApiAutocryptPeer.STATE)) {
|
||||
actualValues.put(ApiAutocryptPeer.STATE,
|
||||
values.getAsLong(ApiAutocryptPeer.STATE));
|
||||
Long gossipMasterKeyId = values.getAsLong(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID);
|
||||
if (gossipMasterKeyId != null && !gossipMasterKeyId.equals(masterKeyId)) {
|
||||
contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(gossipMasterKeyId), null);
|
||||
}
|
||||
|
||||
contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null);
|
||||
|
||||
db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
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 timber.log.Timber;
|
||||
|
||||
|
||||
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) {
|
||||
Date effectiveDate = autocryptPeerUpdate.getEffectiveDate();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Date effectiveDate = autocryptPeerUpdate.getEffectiveDate();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!autocryptPeerUpdate.hasKeyData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.updateKeyGossipFromAutocrypt(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;
|
||||
}
|
||||
}
|
||||
@@ -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.AutocryptRecommendationResult;
|
||||
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;
|
||||
@@ -52,6 +53,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;
|
||||
@@ -66,6 +68,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";
|
||||
|
||||
@@ -98,6 +102,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;
|
||||
}
|
||||
|
||||
@@ -137,6 +144,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();
|
||||
|
||||
@@ -230,81 +238,51 @@ 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 + " INT, " +
|
||||
AutocryptStatus.UID_CANDIDATES + " INT, " +
|
||||
AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT DEFAULT " + AutocryptStatus.AUTOCRYPT_PEER_DISABLED + ", " +
|
||||
AutocryptStatus.AUTOCRYPT_KEY_STATUS + " 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<String, String> 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);
|
||||
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
|
||||
|
||||
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);
|
||||
List<String> plist = Arrays.asList(projection);
|
||||
|
||||
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!");
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao =
|
||||
new AutocryptPeerDataAccessObject(this, callingPackageName);
|
||||
fillTempTableWithAutocryptRecommendations(db, autocryptPeerDao, selectionArgs);
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -326,6 +304,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 + ")");
|
||||
}
|
||||
@@ -351,10 +334,113 @@ 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 fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
|
||||
List<AutocryptRecommendationResult> autocryptStates =
|
||||
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
|
||||
|
||||
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
|
||||
}
|
||||
|
||||
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
|
||||
List<AutocryptRecommendationResult> autocryptRecommendations) {
|
||||
ContentValues cv = new ContentValues();
|
||||
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
|
||||
cv.clear();
|
||||
|
||||
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[] { peerResult.peerId });
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
@@ -41,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;
|
||||
@@ -64,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;
|
||||
@@ -202,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)
|
||||
@@ -239,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,
|
||||
@@ -308,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:
|
||||
@@ -333,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;
|
||||
}
|
||||
@@ -386,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);
|
||||
updateAutocryptPeerImpl(data);
|
||||
|
||||
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mKeyRepository, progressable);
|
||||
|
||||
@@ -475,53 +469,6 @@ public class OpenPgpService extends Service {
|
||||
mApiPendingIntentFactory.createSecurityProblemIntent(packageName, securityProblem, supportOverride));
|
||||
}
|
||||
|
||||
private String updateAutocryptPeerStateFromIntent(Intent data, AutocryptPeerDataAccessObject autocryptPeerDao)
|
||||
throws PgpGeneralException, IOException {
|
||||
String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID);
|
||||
AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE);
|
||||
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 (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 (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) {
|
||||
@@ -616,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;
|
||||
@@ -626,11 +573,16 @@ 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);
|
||||
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);
|
||||
@@ -641,7 +593,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 +752,30 @@ 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 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)) {
|
||||
String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID);
|
||||
AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE);
|
||||
|
||||
if (autocryptPeerUpdate != null) {
|
||||
autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(),
|
||||
mApiPermissionHelper.getCurrentCallingPackage());
|
||||
|
||||
updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao);
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) {
|
||||
Bundle updates = data.getBundleExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES);
|
||||
for (String address : updates.keySet()) {
|
||||
Timber.d(Constants.TAG, "Updating gossip state: " + address);
|
||||
AutocryptPeerUpdate update = updates.getParcelable(address);
|
||||
if (update != null) {
|
||||
autocryptInteractor.updateAutocryptPeerGossipState(address, update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
|
||||
@@ -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);
|
||||
@@ -102,14 +101,14 @@ 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;
|
||||
HashSet<Long> keyIds = new HashSet<>();
|
||||
ArrayList<String> missingEmails = new ArrayList<>();
|
||||
ArrayList<String> duplicateEmails = new ArrayList<>();
|
||||
AutocryptState combinedAutocryptState = null;
|
||||
int combinedAutocryptState = AutocryptStatus.AUTOCRYPT_PEER_DISABLED;
|
||||
|
||||
if (hasAddresses) {
|
||||
HashMap<String, AddressQueryResult> 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<Long> mUserKeyIds;
|
||||
private final HashSet<Long> 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<Long> 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<Long> encryptKeyIds, boolean allKeysConfirmed,
|
||||
AutocryptState combinedAutocryptState) {
|
||||
int combinedAutocryptState) {
|
||||
return new KeyIdResult(encryptKeyIds, allKeysConfirmed, KeyIdResultStatus.OK, combinedAutocryptState);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<List<KeyInfo>> {
|
||||
return;
|
||||
}
|
||||
|
||||
long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId();
|
||||
autocryptPeerDao.updateToSelectedState(duplicateAddress, masterKeyId);
|
||||
long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId();
|
||||
autocryptPeerDao.updateKeyGossipFromDedup(duplicateAddress, new Date(), masterKeyId);
|
||||
|
||||
view.finish();
|
||||
}
|
||||
|
||||
@@ -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<ViewHolder> {
|
||||
|
||||
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<ViewHolder> {
|
||||
@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<ViewHolder> {
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -494,7 +494,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
}
|
||||
|
||||
{ // set icons
|
||||
List<String> packageNames = keyItem.getTrustIdPackages();
|
||||
List<String> packageNames = keyItem.getAutocryptPeerIdPackages();
|
||||
|
||||
if (!keyItem.isSecret() && !packageNames.isEmpty()) {
|
||||
String packageName = packageNames.get(0);
|
||||
@@ -627,7 +627,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
return getInt(index) > 0;
|
||||
}
|
||||
|
||||
public List<String> getTrustIdPackages() {
|
||||
public List<String> getAutocryptPeerIdPackages() {
|
||||
int index = getColumnIndexOrThrow(KeyRings.API_KNOWN_TO_PACKAGE_NAMES);
|
||||
String packageNames = getString(index);
|
||||
if (packageNames == null) {
|
||||
|
||||
@@ -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<List<IdentityInfo>> {
|
||||
|
||||
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<List<IdentityInfo>> {
|
||||
|
||||
private ForceLoadContentObserver identityObserver;
|
||||
|
||||
|
||||
public IdentityLoader(Context context, ContentResolver contentResolver, long masterKeyId, boolean showLinkedIds) {
|
||||
super(context);
|
||||
|
||||
@@ -106,42 +113,36 @@ public class IdentityLoader extends AsyncTaskLoader<List<IdentityInfo>> {
|
||||
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<IdentityInfo> identities) {
|
||||
private void correlateOrAddAutocryptPeers(ArrayList<IdentityInfo> 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<List<IdentityInfo>> {
|
||||
}
|
||||
}
|
||||
|
||||
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<List<IdentityInfo>> {
|
||||
}
|
||||
}
|
||||
|
||||
private static UserIdInfo findUserIdMatchingTrustId(List<IdentityInfo> identities, String autocryptPeer) {
|
||||
private static UserIdInfo findUserIdMatchingAutocryptPeer(List<IdentityInfo> identities, String autocryptPeer) {
|
||||
for (IdentityInfo identityInfo : identities) {
|
||||
if (identityInfo instanceof UserIdInfo) {
|
||||
UserIdInfo userIdInfo = (UserIdInfo) identityInfo;
|
||||
@@ -304,28 +305,28 @@ public class IdentityLoader extends AsyncTaskLoader<List<IdentityInfo>> {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<IdentityInfo>>
|
||||
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<List<IdentityInfo>>
|
||||
}
|
||||
|
||||
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<List<IdentityInfo>>
|
||||
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao =
|
||||
new AutocryptPeerDataAccessObject(context, info.getPackageName());
|
||||
autocryptPeerDao.delete(info.getTrustId());
|
||||
autocryptPeerDao.delete(info.getIdentity());
|
||||
}
|
||||
|
||||
public interface IdentitiesMvpView {
|
||||
|
||||
@@ -392,8 +392,7 @@ public abstract class CursorAdapter<C extends SimpleCursor, VH extends RecyclerV
|
||||
KeychainContract.KeyRings.CREATION,
|
||||
KeychainContract.KeyRings.NAME,
|
||||
KeychainContract.KeyRings.EMAIL,
|
||||
KeychainContract.KeyRings.COMMENT,
|
||||
KeychainContract.KeyRings.API_KNOWN_TO_PACKAGE_NAMES
|
||||
KeychainContract.KeyRings.COMMENT
|
||||
));
|
||||
|
||||
PROJECTION = arr.toArray(new String[arr.size()]);
|
||||
|
||||
Reference in New Issue
Block a user