return key status differently for uid and autocrypt peer in KeychainExternalProvider

This commit is contained in:
Vincent Breitmoser
2017-06-20 18:47:25 +02:00
parent 019e63f681
commit 5e01e41bcd
10 changed files with 220 additions and 278 deletions

View File

@@ -119,37 +119,30 @@ public class AutocryptPeerDataAccessObject {
return null;
}
public void setMasterKeyIdForAutocryptPeer(String autocryptId, long masterKeyId, Date date) {
Date lastUpdated = getLastSeen(autocryptId);
if (lastUpdated != null && lastUpdated.after(date)) {
throw new IllegalArgumentException("Database entry was newer than the one to be inserted! Cannot backdate");
}
ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
cv.put(ApiAutocryptPeer.LAST_SEEN_KEY, date.getTime());
mQueryInterface.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
public void updateToResetState(String autocryptId, Date effectiveDate) {
updateAutocryptState(autocryptId, effectiveDate, null, ApiAutocryptPeer.RESET);
}
public void updateToResetState(String autocryptId, Date effectiveDate) {
updateAutocryptState(autocryptId, effectiveDate, ApiAutocryptPeer.RESET);
public void updateToGossipState(String autocryptId, Date effectiveDate, long masterKeyId) {
updateAutocryptState(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP);
}
public void updateToMutualState(String autocryptId, Date effectiveDate, long masterKeyId) {
setMasterKeyIdForAutocryptPeer(autocryptId, masterKeyId, effectiveDate);
updateAutocryptState(autocryptId, effectiveDate, ApiAutocryptPeer.MUTUAL);
updateAutocryptState(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.MUTUAL);
}
public void updateToAvailableState(String autocryptId, Date effectiveDate, long masterKeyId) {
setMasterKeyIdForAutocryptPeer(autocryptId, masterKeyId, effectiveDate);
updateAutocryptState(autocryptId, effectiveDate, ApiAutocryptPeer.AVAILABLE);
updateAutocryptState(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.AVAILABLE);
}
private void updateAutocryptState(String autocryptId, Date date, int status) {
private void updateAutocryptState(String autocryptId, Date date, Long masterKeyId, int status) {
ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.MASTER_KEY_ID, (Integer) null);
cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime());
cv.put(ApiAutocryptPeer.STATUS, status);
if (masterKeyId != null) {
cv.put(ApiAutocryptPeer.LAST_SEEN_KEY, masterKeyId);
}
cv.put(ApiAutocryptPeer.STATE, status);
mQueryInterface.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
}
}

View File

@@ -100,7 +100,7 @@ public class KeychainContract {
String IDENTIFIER = "identifier";
String LAST_SEEN = "last_updated";
String LAST_SEEN_KEY = "last_seen_key";
String STATUS = "status";
String STATE = "state";
String MASTER_KEY_ID = "master_key_id";
}

View File

@@ -164,7 +164,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ ApiAutocryptPeerColumns.IDENTIFIER + " TEXT NOT NULL, "
+ ApiAutocryptPeerColumns.LAST_SEEN + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.LAST_SEEN_KEY + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.STATUS + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.STATE + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NULL, "
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "

View File

@@ -26,9 +26,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPee
public class KeychainExternalContract {
public static final int KEY_STATUS_UNAVAILABLE = 0;
public static final int KEY_STATUS_UNVERIFIED = 1;
public static final int KEY_STATUS_VERIFIED = 2;
// this is in KeychainExternalContract already, but we want to be double
// sure this isn't mixed up with the internal one!
@@ -36,15 +33,31 @@ public class KeychainExternalContract {
private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri
.parse("content://" + CONTENT_AUTHORITY_EXTERNAL);
public static final String BASE_EMAIL_STATUS = "email_status";
public static final String BASE_AUTOCRYPT_PEER_STATUS = "autocrypt_status";
public static final String BASE_AUTOCRYPT_PEERS = "autocrypt_peers";
public static class EmailStatus implements BaseColumns {
public static final String EMAIL_ADDRESS = "email_address";
public static final String USER_ID = "user_id";
public static final String USER_ID_STATUS = "email_status";
public static final String MASTER_KEY_ID = "master_key_id";
public static final String ADDRESS = "address";
public static final String UID_ADDRESS = "uid_address";
public static final String UID_KEY_STATUS = "uid_key_status";
public static final String UID_MASTER_KEY_ID = "uid_master_key_id";
public static final String UID_CANDIDATES = "uid_candidates";
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 KEY_STATUS_UNAVAILABLE = 0;
public static final int KEY_STATUS_UNVERIFIED = 1;
public static final int KEY_STATUS_VERIFIED = 2;
public static final int AUTOCRYPT_PEER_RESET = 0;
public static final int AUTOCRYPT_PEER_GOSSIP = 1;
public static final int AUTOCRYPT_PEER_AVAILABLE = 2;
public static final int AUTOCRYPT_PEER_MUTUAL = 3;
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
.appendPath(BASE_EMAIL_STATUS).build();
@@ -53,19 +66,6 @@ public class KeychainExternalContract {
"vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status";
}
public static class AutocryptPeerStatus implements BaseColumns {
public static final String EMAIL_ADDRESS = "email_address";
public static final String MASTER_KEY_ID = "master_key_id";
public static final String AUTOCRYPT_PEER_LAST_SEEN = "autocrypt_peer_last_seen";
public static final String AUTOCRYPT_PEER_STATUS = "autocrypt_peer_state";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
.appendPath(BASE_EMAIL_STATUS).build();
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.autocrypt_status";
}
public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
.appendPath(BASE_AUTOCRYPT_PEERS).build();

View File

@@ -999,7 +999,6 @@ public class KeychainProvider extends ContentProvider {
}
case TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID: {
Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID);
long updateTime = values.getAsLong(ApiAutocryptPeer.LAST_SEEN);
if (masterKeyId == null) {
throw new IllegalArgumentException("master_key_id must be a non-null value!");
}
@@ -1009,7 +1008,20 @@ public class KeychainProvider extends ContentProvider {
actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, packageName);
actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment());
actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
actualValues.put(ApiAutocryptPeer.LAST_SEEN, updateTime);
Long newLastSeen = values.getAsLong(ApiAutocryptPeer.LAST_SEEN);
if (newLastSeen != null) {
actualValues.put(ApiAutocryptPeer.LAST_SEEN, newLastSeen);
}
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));
}
try {
db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues);

View File

@@ -21,7 +21,6 @@ import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import android.content.ContentProvider;
import android.content.ContentValues;
@@ -48,7 +47,6 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.ApiAutocryptPeer;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptPeerStatus;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
import org.sufficientlysecure.keychain.util.Log;
@@ -59,8 +57,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
private static final int AUTOCRYPT_PEER = 201;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302;
private static final int AUTOCRYPT_PEER_STATUS = 401;
private static final int AUTOCRYPT_PEER_STATUS_INTERNAL = 402;
public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses";
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
@@ -91,9 +87,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_PEERS + "/*", AUTOCRYPT_PEER);
matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_PEER_STATUS, AUTOCRYPT_PEER_STATUS);
matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_PEER_STATUS + "/*", AUTOCRYPT_PEER_STATUS_INTERNAL);
// 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);
@@ -120,9 +113,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
final int match = mUriMatcher.match(uri);
switch (match) {
case EMAIL_STATUS:
case EMAIL_STATUS_INTERNAL:
case AUTOCRYPT_PEER_STATUS:
case AUTOCRYPT_PEER_STATUS_INTERNAL:
return EmailStatus.CONTENT_TYPE;
case API_APPS:
@@ -178,21 +168,38 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
HashMap<String, String> projectionMap = new HashMap<>();
projectionMap.put(EmailStatus._ID, "email AS _id");
projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS);
projectionMap.put(EmailStatus.USER_ID,
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
projectionMap.put(EmailStatus.ADDRESS, // this is actually the queried address
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.ADDRESS);
projectionMap.put(EmailStatus.UID_ADDRESS,
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.UID_ADDRESS);
// we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified"
projectionMap.put(EmailStatus.USER_ID_STATUS, "CASE ( MIN (certs_user_id." + Certs.VERIFIED + " ) ) "
projectionMap.put(EmailStatus.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 " + Certs.VERIFIED_SELF + " THEN " + EmailStatus.KEY_STATUS_UNVERIFIED
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN " + EmailStatus.KEY_STATUS_VERIFIED
+ " WHEN NULL THEN NULL"
+ " END AS " + EmailStatus.USER_ID_STATUS);
projectionMap.put(EmailStatus.MASTER_KEY_ID,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID);
projectionMap.put(EmailStatus.USER_ID,
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
+ " END AS " + EmailStatus.UID_KEY_STATUS);
projectionMap.put(EmailStatus.UID_MASTER_KEY_ID,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.UID_MASTER_KEY_ID);
projectionMap.put(EmailStatus.UID_CANDIDATES,
"COUNT(DISTINCT " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID +
") AS " + EmailStatus.UID_CANDIDATES);
projectionMap.put(EmailStatus.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 " + EmailStatus.KEY_STATUS_UNVERIFIED
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN " + EmailStatus.KEY_STATUS_VERIFIED
+ " WHEN NULL THEN NULL"
+ " END AS " + EmailStatus.AUTOCRYPT_KEY_STATUS);
projectionMap.put(EmailStatus.AUTOCRYPT_MASTER_KEY_ID,
Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + " AS " + EmailStatus.AUTOCRYPT_MASTER_KEY_ID);
projectionMap.put(EmailStatus.AUTOCRYPT_PEER_STATE, Tables.API_AUTOCRYPT_PEERS + "." +
ApiAutocryptPeer.STATE + " AS " + EmailStatus.AUTOCRYPT_LAST_SEEN_KEY);
projectionMap.put(EmailStatus.AUTOCRYPT_LAST_SEEN, Tables.API_AUTOCRYPT_PEERS + "." +
ApiAutocryptPeer.LAST_SEEN + " AS " + EmailStatus.AUTOCRYPT_LAST_SEEN);
projectionMap.put(EmailStatus.AUTOCRYPT_LAST_SEEN_KEY, Tables.API_AUTOCRYPT_PEERS + "." +
ApiAutocryptPeer.LAST_SEEN_KEY + " AS " + EmailStatus.AUTOCRYPT_LAST_SEEN_KEY);
qb.setProjectionMap(projectionMap);
if (projection == null) {
@@ -209,66 +216,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
+ 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
+ ")"
);
// in case there are multiple verifying certificates
groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES;
List<String> plist = Arrays.asList(projection);
if (plist.contains(EmailStatus.USER_ID)) {
groupBy += ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID;
}
if (TextUtils.isEmpty(sortOrder)) {
sortOrder = EmailStatus.EMAIL_ADDRESS;
}
// uri to watch is all /key_rings/
uri = KeyRings.CONTENT_URI;
break;
}
case AUTOCRYPT_PEER_STATUS_INTERNAL:
if (!BuildConfig.APPLICATION_ID.equals(callingPackageName)) {
throw new AccessControlException("This URI can only be called internally!");
}
// override package name to use any external
// callingPackageName = uri.getLastPathSegment();
case AUTOCRYPT_PEER_STATUS: {
boolean callerIsAllowed = (match == AUTOCRYPT_PEER_STATUS_INTERNAL) || mApiPermissionHelper.isAllowedIgnoreErrors();
if (!callerIsAllowed) {
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);");
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(AutocryptPeerStatus._ID, "email AS _id");
projectionMap.put(AutocryptPeerStatus.EMAIL_ADDRESS, // this is actually the queried address
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + AutocryptPeerStatus.EMAIL_ADDRESS);
projectionMap.put(AutocryptPeerStatus.AUTOCRYPT_PEER_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 " + AutocryptPeerStatus.AUTOCRYPT_PEER_STATUS);
projectionMap.put(AutocryptPeerStatus.MASTER_KEY_ID,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + AutocryptPeerStatus.MASTER_KEY_ID);
projectionMap.put(AutocryptPeerStatus.AUTOCRYPT_PEER_LAST_SEEN, Tables.API_AUTOCRYPT_PEERS + "." +
ApiAutocryptPeer.LAST_SEEN + " AS " + AutocryptPeerStatus.AUTOCRYPT_PEER_LAST_SEEN);
qb.setProjectionMap(projectionMap);
if (projection == null) {
throw new IllegalArgumentException("Please provide a projection!");
}
qb.setTables(
TEMP_TABLE_QUERIED_ADDRESSES
+ " 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 + "\""
@@ -279,10 +226,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
);
// in case there are multiple verifying certificates
groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES;
List<String> plist = Arrays.asList(projection);
if (TextUtils.isEmpty(sortOrder)) {
sortOrder = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES;
sortOrder = EmailStatus.ADDRESS;
}
// uri to watch is all /key_rings/
@@ -305,6 +251,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER);
projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.MASTER_KEY_ID);
projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN);
projectionMap.put(ApiAutocryptPeer.LAST_SEEN_KEY, ApiAutocryptPeer.LAST_SEEN_KEY);
qb.setProjectionMap(projectionMap);
qb.setTables(Tables.API_AUTOCRYPT_PEERS);

View File

@@ -606,7 +606,7 @@ public class OpenPgpService extends Service {
long masterKeyId = signatureResult.getKeyId();
if (autocryptPeerMasterKeyId == null) {
autocryptPeerentityDao.setMasterKeyIdForAutocryptPeer(autocryptPeerentity, masterKeyId, new Date());
autocryptPeerentityDao.updateToGossipState(autocryptPeerentity, new Date(), masterKeyId);
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW);
} else if (masterKeyId == autocryptPeerMasterKeyId) {
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK);

View File

@@ -4,7 +4,6 @@ package org.sufficientlysecure.keychain.remote;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -17,7 +16,6 @@ import android.support.annotation.VisibleForTesting;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptPeerStatus;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
@@ -26,22 +24,21 @@ import org.sufficientlysecure.keychain.util.Log;
class OpenPgpServiceKeyIdExtractor {
@VisibleForTesting
static final String[] PROJECTION_MAIL_STATUS = {
EmailStatus.EMAIL_ADDRESS,
EmailStatus.MASTER_KEY_ID,
EmailStatus.USER_ID_STATUS,
EmailStatus.ADDRESS,
EmailStatus.UID_MASTER_KEY_ID,
EmailStatus.UID_KEY_STATUS,
EmailStatus.UID_CANDIDATES,
EmailStatus.AUTOCRYPT_MASTER_KEY_ID,
EmailStatus.AUTOCRYPT_KEY_STATUS,
EmailStatus.AUTOCRYPT_PEER_STATE
};
private static final int INDEX_EMAIL_ADDRESS = 0;
private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_USER_ID_STATUS = 2;
static final String[] PROJECTION_AUTOCRYPT = {
AutocryptPeerStatus.EMAIL_ADDRESS,
AutocryptPeerStatus.MASTER_KEY_ID,
AutocryptPeerStatus.AUTOCRYPT_PEER_STATUS,
};
private static final int INDEX_AUTOCRYPT_ADDRESS = 0;
private static final int INDEX_AUTOCRYPT_MASTER_KEY_ID = 1;
private static final int INDEX_AUTOCRYPT_STATUS = 2;
private static final int INDEX_USER_ID_CANDIDATES = 3;
private static final int INDEX_AUTOCRYPT_MASTER_KEY_ID = 4;
private static final int INDEX_AUTOCRYPT_KEY_STATUS = 5;
private static final int INDEX_AUTOCRYPT_PEER_STATE = 6;
private final ApiPendingIntentFactory apiPendingIntentFactory;
@@ -97,33 +94,33 @@ class OpenPgpServiceKeyIdExtractor {
ArrayList<String> duplicateEmails = new ArrayList<>();
if (hasAddresses) {
HashMap<String, UserIdStatus> keyRows = getStatusMapForQueriedAddresses(encryptionAddresses, callingPackageName);
HashMap<String, AutocryptRecommendation> autocryptRows = getAutocryptRecommendationsForQueriedAddresses(
HashMap<String, AddressQueryResult> userIdEntries = getStatusMapForQueriedAddresses(
encryptionAddresses, callingPackageName);
boolean anyKeyNotVerified = false;
for (Entry<String, UserIdStatus> entry : keyRows.entrySet()) {
String queriedAddress = entry.getKey();
UserIdStatus userIdStatus = entry.getValue();
for (String queriedAddress : encryptionAddresses) {
AddressQueryResult addressQueryResult = userIdEntries.get(queriedAddress);
if (addressQueryResult == null) {
throw new IllegalStateException("No result for address - shouldn't happen!");
}
if (userIdStatus.masterKeyId == null) {
if (addressQueryResult.uidMasterKeyId != null) {
keyIds.add(addressQueryResult.uidMasterKeyId);
if (addressQueryResult.uidHasMultipleCandidates) {
duplicateEmails.add(queriedAddress);
}
} else {
missingEmails.add(queriedAddress);
continue;
}
keyIds.add(userIdStatus.masterKeyId);
if (userIdStatus.hasDuplicate) {
duplicateEmails.add(queriedAddress);
}
if (userIdStatus.userIdVerified != KeychainExternalContract.KEY_STATUS_VERIFIED &&
userIdStatus.autocryptPeerVerified != KeychainExternalContract.KEY_STATUS_VERIFIED) {
if (addressQueryResult.uidKeyStatus != EmailStatus.KEY_STATUS_VERIFIED) {
anyKeyNotVerified = true;
}
}
if (keyRows.size() != encryptionAddresses.length) {
if (userIdEntries.size() != encryptionAddresses.length) {
Log.e(Constants.TAG, "Number of rows doesn't match number of retrieved rows! Probably a bug?");
}
@@ -151,8 +148,8 @@ class OpenPgpServiceKeyIdExtractor {
* verification status exist, the first one is returned and marked as having a duplicate.
*/
@NonNull
private HashMap<String, UserIdStatus> getStatusMapForQueriedAddresses(String[] encryptionUserIds, String callingPackageName) {
HashMap<String,UserIdStatus> keyRows = new HashMap<>();
private HashMap<String, AddressQueryResult> getStatusMapForQueriedAddresses(String[] encryptionUserIds, String callingPackageName) {
HashMap<String,AddressQueryResult> keyRows = new HashMap<>();
Uri queryUri = EmailStatus.CONTENT_URI.buildUpon().appendPath(callingPackageName).build();
Cursor cursor = contentResolver.query(queryUri, PROJECTION_MAIL_STATUS, null, encryptionUserIds, null);
if (cursor == null) {
@@ -162,36 +159,21 @@ class OpenPgpServiceKeyIdExtractor {
try {
while (cursor.moveToNext()) {
String queryAddress = cursor.getString(INDEX_EMAIL_ADDRESS);
Long masterKeyId = cursor.isNull(INDEX_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_MASTER_KEY_ID);
int userIdStatus = cursor.getInt(INDEX_USER_ID_STATUS);
Long uidMasterKeyId =
cursor.isNull(INDEX_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_MASTER_KEY_ID);
int uidKeyStatus = cursor.getInt(INDEX_USER_ID_STATUS);
boolean uidHasMultipleCandidates = cursor.getInt(INDEX_USER_ID_CANDIDATES) > 1;
AutocryptRecommendation autocryptRecommendation;
if (cursor.getInt(INDEX_AUTOCRYPT_PEER_STATUS) == KeychainExternalContract.KEY_STATUS_UNAVAILABLE) {
autocryptRecommendation = AutocryptRecommendation.UNAVAILABLE;
} else {
// TODO encourage/discourage, based on gossip/ state
autocryptRecommendation = AutocryptRecommendation.AVAILABLE;
}
UserIdStatus status = new UserIdStatus(masterKeyId, userIdStatus, autocryptRecommendation);
Long autocryptMasterKeyId =
cursor.isNull(INDEX_AUTOCRYPT_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_AUTOCRYPT_MASTER_KEY_ID);
int autocryptKeyStatus = cursor.getInt(INDEX_AUTOCRYPT_KEY_STATUS);
int autocryptPeerStatus = cursor.getInt(INDEX_AUTOCRYPT_PEER_STATE);
boolean seenBefore = keyRows.containsKey(queryAddress);
if (!seenBefore) {
keyRows.put(queryAddress, status);
continue;
}
AddressQueryResult
status = new AddressQueryResult(uidMasterKeyId, uidKeyStatus, uidHasMultipleCandidates,
autocryptMasterKeyId, autocryptKeyStatus, autocryptPeerStatus);
UserIdStatus previousUserIdStatus = keyRows.get(queryAddress);
if (previousUserIdStatus.autocryptPeerVerified != KeychainExternalContract.KEY_STATUS_UNAVAILABLE) {
continue;
}
if (previousUserIdStatus.masterKeyId == null) {
keyRows.put(queryAddress, status);
} else if (previousUserIdStatus.userIdVerified < status.userIdVerified) {
keyRows.put(queryAddress, status);
} else if (previousUserIdStatus.userIdVerified == status.userIdVerified) {
previousUserIdStatus.hasDuplicate = true;
}
keyRows.put(queryAddress, status);
}
} finally {
cursor.close();
@@ -199,51 +181,28 @@ class OpenPgpServiceKeyIdExtractor {
return keyRows;
}
/** 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
* verification status exist, the first one is returned and marked as having a duplicate.
*/
@NonNull
private HashMap<String, AutocryptRecommendation> getAutocryptRecommendationsForQueriedAddresses(
String[] encryptionUserIds, String callingPackageName) {
Uri queryUri = AutocryptPeerStatus.CONTENT_URI.buildUpon().appendPath(callingPackageName).build();
Cursor cursor = contentResolver.query(queryUri, PROJECTION_AUTOCRYPT, null, encryptionUserIds, null);
if (cursor == null) {
throw new IllegalStateException("Internal error, received null cursor!");
}
private static class AddressQueryResult {
private final Long uidMasterKeyId;
private final int uidKeyStatus;
private boolean uidHasMultipleCandidates;
private final Long autocryptMasterKeyId;
private final int autocryptKeyStatus;
private final int autocryptState;
try {
HashMap<String,UserIdStatus> keyRows = new HashMap<>();
while (cursor.moveToNext()) {
String queryAddress = cursor.getString(INDEX_AUTOCRYPT_ADDRESS);
Long masterKeyId = cursor.isNull(INDEX_AUTOCRYPT_MASTER_KEY_ID) ?
null : cursor.getLong(INDEX_AUTOCRYPT_MASTER_KEY_ID);
int autocryptStatus = cursor.getInt(INDEX_AUTOCRYPT_STATUS);
AutocryptRecommendation autocryptRecommendation;
if (cursor.getInt(INDEX_AUTOCRYPT_STATUS) == KeychainExternalContract.KEY_STATUS_UNAVAILABLE) {
autocryptRecommendation = AutocryptRecommendation.UNAVAILABLE;
} else {
// TODO encourage/discourage, based on gossip/ state
autocryptRecommendation = AutocryptRecommendation.AVAILABLE;
}
}
} finally {
cursor.close();
AddressQueryResult(Long uidMasterKeyId, int uidKeyStatus, boolean uidHasMultipleCandidates, Long autocryptMasterKeyId,
int autocryptKeyStatus,
int autocryptState) {
this.uidMasterKeyId = uidMasterKeyId;
this.uidKeyStatus = uidKeyStatus;
this.uidHasMultipleCandidates = uidHasMultipleCandidates;
this.autocryptMasterKeyId = autocryptMasterKeyId;
this.autocryptKeyStatus = autocryptKeyStatus;
this.autocryptState = autocryptState;
}
return keyRows;
}
private static class UserIdStatus {
private final Long masterKeyId;
private final int userIdVerified;
private boolean hasDuplicate;
UserIdStatus(Long masterKeyId, int userIdVerified) {
this.masterKeyId = masterKeyId;
this.userIdVerified = userIdVerified;
}
enum AutocryptState {
UNAVAILABLE, DISCOURAGE, AVAILABLE, MUTUAL
}
static class KeyIdResult {
@@ -317,10 +276,6 @@ class OpenPgpServiceKeyIdExtractor {
}
}
enum AutocryptRecommendation {
UNAVAILABLE, DISCOURAGE, AVAILABLE, MUTUAL
}
enum KeyIdResultStatus {
OK, MISSING, DUPLICATE, NO_KEYS, NO_KEYS_ERROR
}