full-on autocrypt!

This commit is contained in:
Vincent Breitmoser
2017-06-16 18:29:01 +02:00
parent 1e620e01aa
commit 019e63f681
11 changed files with 297 additions and 125 deletions

View File

@@ -85,13 +85,30 @@ public class AutocryptPeerDataAccessObject {
return null; return null;
} }
public Date getLastUpdateForAutocryptPeer(String autocryptId) { public Date getLastSeen(String autocryptId) {
Cursor cursor = mQueryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), Cursor cursor = mQueryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
null, null, null, null); null, null, null, null);
try { try {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
long lastUpdated = cursor.getColumnIndex(ApiAutocryptPeer.LAST_UPDATED); long lastUpdated = cursor.getColumnIndex(ApiAutocryptPeer.LAST_SEEN);
return new Date(lastUpdated);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public Date getLastSeenKey(String autocryptId) {
Cursor cursor = mQueryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long lastUpdated = cursor.getColumnIndex(ApiAutocryptPeer.LAST_SEEN_KEY);
return new Date(lastUpdated); return new Date(lastUpdated);
} }
} finally { } finally {
@@ -103,14 +120,36 @@ public class AutocryptPeerDataAccessObject {
} }
public void setMasterKeyIdForAutocryptPeer(String autocryptId, long masterKeyId, Date date) { public void setMasterKeyIdForAutocryptPeer(String autocryptId, long masterKeyId, Date date) {
Date lastUpdated = getLastUpdateForAutocryptPeer(autocryptId); Date lastUpdated = getLastSeen(autocryptId);
if (lastUpdated != null && lastUpdated.after(date)) { if (lastUpdated != null && lastUpdated.after(date)) {
throw new IllegalArgumentException("Database entry was newer than the one to be inserted! Cannot backdate"); throw new IllegalArgumentException("Database entry was newer than the one to be inserted! Cannot backdate");
} }
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
cv.put(ApiAutocryptPeer.LAST_UPDATED, date.getTime()); 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, ApiAutocryptPeer.RESET);
}
public void updateToMutualState(String autocryptId, Date effectiveDate, long masterKeyId) {
setMasterKeyIdForAutocryptPeer(autocryptId, masterKeyId, effectiveDate);
updateAutocryptState(autocryptId, effectiveDate, ApiAutocryptPeer.MUTUAL);
}
public void updateToAvailableState(String autocryptId, Date effectiveDate, long masterKeyId) {
setMasterKeyIdForAutocryptPeer(autocryptId, masterKeyId, effectiveDate);
updateAutocryptState(autocryptId, effectiveDate, ApiAutocryptPeer.AVAILABLE);
}
private void updateAutocryptState(String autocryptId, Date date, int status) {
ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.MASTER_KEY_ID, (Integer) null);
cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime());
cv.put(ApiAutocryptPeer.STATUS, status);
mQueryInterface.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); mQueryInterface.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
} }
} }

View File

@@ -98,7 +98,9 @@ public class KeychainContract {
interface ApiAutocryptPeerColumns { interface ApiAutocryptPeerColumns {
String PACKAGE_NAME = "package_name"; String PACKAGE_NAME = "package_name";
String IDENTIFIER = "identifier"; String IDENTIFIER = "identifier";
String LAST_UPDATED = "last_updated"; String LAST_SEEN = "last_updated";
String LAST_SEEN_KEY = "last_seen_key";
String STATUS = "status";
String MASTER_KEY_ID = "master_key_id"; String MASTER_KEY_ID = "master_key_id";
} }
@@ -349,6 +351,11 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_AUTOCRYPT_PEERS).build(); .appendPath(BASE_AUTOCRYPT_PEERS).build();
public static final int RESET = 0;
public static final int GOSSIP = 1;
public static final int AVAILABLE = 2;
public static final int MUTUAL = 3;
public static Uri buildByKeyUri(Uri uri) { public static Uri buildByKeyUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).build(); return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).build();
} }

View File

@@ -162,8 +162,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
"CREATE TABLE IF NOT EXISTS " + Tables.API_AUTOCRYPT_PEERS + " (" "CREATE TABLE IF NOT EXISTS " + Tables.API_AUTOCRYPT_PEERS + " ("
+ ApiAutocryptPeerColumns.PACKAGE_NAME + " TEXT NOT NULL, " + ApiAutocryptPeerColumns.PACKAGE_NAME + " TEXT NOT NULL, "
+ ApiAutocryptPeerColumns.IDENTIFIER + " TEXT NOT NULL, " + ApiAutocryptPeerColumns.IDENTIFIER + " TEXT NOT NULL, "
+ ApiAutocryptPeerColumns.LAST_UPDATED + " INTEGER NOT NULL, " + ApiAutocryptPeerColumns.LAST_SEEN + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " + ApiAutocryptPeerColumns.LAST_SEEN_KEY + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.STATUS + " INTEGER NOT NULL, "
+ ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NULL, "
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", " + "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
+ ApiAutocryptPeerColumns.IDENTIFIER + "), " + ApiAutocryptPeerColumns.IDENTIFIER + "), "
+ "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES " + "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES "

View File

@@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPee
public class KeychainExternalContract { 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_UNVERIFIED = 1;
public static final int KEY_STATUS_VERIFIED = 2; public static final int KEY_STATUS_VERIFIED = 2;
@@ -35,6 +36,7 @@ public class KeychainExternalContract {
private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri
.parse("content://" + CONTENT_AUTHORITY_EXTERNAL); .parse("content://" + CONTENT_AUTHORITY_EXTERNAL);
public static final String BASE_EMAIL_STATUS = "email_status"; 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 final String BASE_AUTOCRYPT_PEERS = "autocrypt_peers";
@@ -43,8 +45,6 @@ public class KeychainExternalContract {
public static final String USER_ID = "user_id"; public static final String USER_ID = "user_id";
public static final String USER_ID_STATUS = "email_status"; public static final String USER_ID_STATUS = "email_status";
public static final String MASTER_KEY_ID = "master_key_id"; 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_STATE = "autocrypt_peer_state";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
.appendPath(BASE_EMAIL_STATUS).build(); .appendPath(BASE_EMAIL_STATUS).build();
@@ -53,6 +53,19 @@ public class KeychainExternalContract {
"vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; "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 class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
.appendPath(BASE_AUTOCRYPT_PEERS).build(); .appendPath(BASE_AUTOCRYPT_PEERS).build();

View File

@@ -676,7 +676,7 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME); projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME);
projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER); projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER);
projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.MASTER_KEY_ID); projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.MASTER_KEY_ID);
projectionMap.put(ApiAutocryptPeer.LAST_UPDATED, ApiAutocryptPeer.LAST_UPDATED); projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN);
qb.setProjectionMap(projectionMap); qb.setProjectionMap(projectionMap);
qb.setTables(Tables.API_AUTOCRYPT_PEERS); qb.setTables(Tables.API_AUTOCRYPT_PEERS);
@@ -999,7 +999,7 @@ public class KeychainProvider extends ContentProvider {
} }
case TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID: { case TRUST_IDS_BY_PACKAGE_NAME_AND_TRUST_ID: {
Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID); Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID);
long updateTime = values.getAsLong(ApiAutocryptPeer.LAST_UPDATED); long updateTime = values.getAsLong(ApiAutocryptPeer.LAST_SEEN);
if (masterKeyId == null) { if (masterKeyId == null) {
throw new IllegalArgumentException("master_key_id must be a non-null value!"); throw new IllegalArgumentException("master_key_id must be a non-null value!");
} }
@@ -1009,7 +1009,7 @@ public class KeychainProvider extends ContentProvider {
actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, packageName);
actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment()); actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment());
actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
actualValues.put(ApiAutocryptPeer.LAST_UPDATED, updateTime); actualValues.put(ApiAutocryptPeer.LAST_SEEN, updateTime);
try { try {
db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues);

View File

@@ -48,6 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.ApiAutocryptPeer; 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.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@@ -58,6 +59,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
private static final int AUTOCRYPT_PEER = 201; private static final int AUTOCRYPT_PEER = 201;
private static final int API_APPS = 301; private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302; 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_QUERIED_ADDRESSES = "queried_addresses";
public static final String TEMP_TABLE_COLUMN_ADDRES = "address"; public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
@@ -88,6 +91,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_PEERS + "/*", AUTOCRYPT_PEER); 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! // 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_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
@@ -114,6 +120,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
final int match = mUriMatcher.match(uri); final int match = mUriMatcher.match(uri);
switch (match) { switch (match) {
case EMAIL_STATUS: case EMAIL_STATUS:
case EMAIL_STATUS_INTERNAL:
case AUTOCRYPT_PEER_STATUS:
case AUTOCRYPT_PEER_STATUS_INTERNAL:
return EmailStatus.CONTENT_TYPE; return EmailStatus.CONTENT_TYPE;
case API_APPS: case API_APPS:
@@ -180,18 +189,10 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED
+ " WHEN NULL THEN NULL" + " WHEN NULL THEN NULL"
+ " END AS " + EmailStatus.USER_ID_STATUS); + " END AS " + EmailStatus.USER_ID_STATUS);
projectionMap.put(EmailStatus.AUTOCRYPT_PEER_STATE, "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 " + EmailStatus.AUTOCRYPT_PEER_STATE);
projectionMap.put(EmailStatus.MASTER_KEY_ID, projectionMap.put(EmailStatus.MASTER_KEY_ID,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID); Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID);
projectionMap.put(EmailStatus.USER_ID, projectionMap.put(EmailStatus.USER_ID,
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
projectionMap.put(EmailStatus.AUTOCRYPT_PEER_LAST_SEEN, Tables.API_AUTOCRYPT_PEERS + "." +
ApiAutocryptPeer.LAST_UPDATED + " AS " + EmailStatus.AUTOCRYPT_PEER_LAST_SEEN);
qb.setProjectionMap(projectionMap); qb.setProjectionMap(projectionMap);
if (projection == null) { if (projection == null) {
@@ -208,6 +209,66 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = certs_user_id." + Certs.MASTER_KEY_ID + 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 + " 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 (" + " LEFT JOIN " + Tables.API_AUTOCRYPT_PEERS + " ON ("
+ Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.IDENTIFIER + " LIKE queried_addresses.address" + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.IDENTIFIER + " LIKE queried_addresses.address"
+ " AND " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = \"" + callingPackageName + "\"" + " AND " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = \"" + callingPackageName + "\""
@@ -219,12 +280,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
// in case there are multiple verifying certificates // in case there are multiple verifying certificates
groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES; groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES;
List<String> plist = Arrays.asList(projection); List<String> plist = Arrays.asList(projection);
if (plist.contains(EmailStatus.USER_ID)) {
groupBy += ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID;
}
if (TextUtils.isEmpty(sortOrder)) { if (TextUtils.isEmpty(sortOrder)) {
sortOrder = EmailStatus.EMAIL_ADDRESS; sortOrder = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES;
} }
// uri to watch is all /key_rings/ // uri to watch is all /key_rings/
@@ -246,7 +304,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
projectionMap.put(ApiAutocryptPeer._ID, "oid AS " + ApiAutocryptPeer._ID); projectionMap.put(ApiAutocryptPeer._ID, "oid AS " + ApiAutocryptPeer._ID);
projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER); projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER);
projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.MASTER_KEY_ID); projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, ApiAutocryptPeer.MASTER_KEY_ID);
projectionMap.put(ApiAutocryptPeer.LAST_UPDATED, ApiAutocryptPeer.LAST_UPDATED); projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN);
qb.setProjectionMap(projectionMap); qb.setProjectionMap(projectionMap);
qb.setTables(Tables.API_AUTOCRYPT_PEERS); qb.setTables(Tables.API_AUTOCRYPT_PEERS);
@@ -343,7 +401,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, mApiPermissionHelper.getCurrentCallingPackage()); actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, mApiPermissionHelper.getCurrentCallingPackage());
actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment()); actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment());
actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
actualValues.put(ApiAutocryptPeer.LAST_UPDATED, new Date().getTime() / 1000); actualValues.put(ApiAutocryptPeer.LAST_SEEN, new Date().getTime() / 1000);
SQLiteDatabase db = getDb().getWritableDatabase(); SQLiteDatabase db = getDb().getWritableDatabase();
try { try {

View File

@@ -41,10 +41,11 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.bouncycastle.bcpg.ArmoredOutputStream; 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.IOpenPgpService;
import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.AutocryptPeerUpdate;
import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult; import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult;
@@ -68,12 +69,12 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;
import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
@@ -198,7 +199,7 @@ public class OpenPgpService extends Service {
} }
private Intent encryptAndSignImpl(Intent data, InputStream inputStream, private Intent encryptAndSignImpl(Intent data, InputStream inputStream,
OutputStream outputStream, boolean sign) { OutputStream outputStream, boolean sign, boolean isQueryAutocryptStatus) {
try { try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME); String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME);
@@ -237,11 +238,10 @@ public class OpenPgpService extends Service {
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false, KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false,
mApiPermissionHelper.getCurrentCallingPackage()); mApiPermissionHelper.getCurrentCallingPackage());
boolean isDryRun = data.getBooleanExtra(OpenPgpApi.EXTRA_DRY_RUN, false);
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
KeyIdResultStatus keyIdResultStatus = keyIdResult.getStatus(); KeyIdResultStatus keyIdResultStatus = keyIdResult.getStatus();
if (isDryRun) { if (isQueryAutocryptStatus) {
return getDryRunStatusResult(keyIdResult); return getAutocryptStatusResult(keyIdResult);
} }
if (keyIdResult.hasKeySelectionPendingIntent()) { if (keyIdResult.hasKeySelectionPendingIntent()) {
@@ -302,32 +302,34 @@ public class OpenPgpService extends Service {
} }
@NonNull @NonNull
private Intent getDryRunStatusResult(KeyIdResult keyIdResult) { private Intent getAutocryptStatusResult(KeyIdResult keyIdResult) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
switch (keyIdResult.getStatus()) { switch (keyIdResult.getStatus()) {
case MISSING: { case MISSING: {
return createErrorResultIntent(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_UNAVAILABLE);
"missing keys in opportunistic mode"); break;
} }
case NO_KEYS: case NO_KEYS:
case NO_KEYS_ERROR: { case NO_KEYS_ERROR: {
return createErrorResultIntent(OpenPgpError.NO_USER_IDS, "empty recipient list"); result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_UNAVAILABLE);
break;
} }
case DUPLICATE: { case DUPLICATE: {
Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_DISCOURAGE);
result.putExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, false); break;
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} }
case OK: { case OK: {
Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_AVAILABLE);
result.putExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, keyIdResult.isAllKeysConfirmed()); break;
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} }
default: { default: {
throw new IllegalStateException("unhandled case!"); throw new IllegalStateException("unhandled case!");
} }
} }
return result;
} }
private Intent decryptAndVerifyImpl(Intent data, InputStream inputStream, OutputStream outputStream, private Intent decryptAndVerifyImpl(Intent data, InputStream inputStream, OutputStream outputStream,
@@ -452,35 +454,45 @@ public class OpenPgpService extends Service {
private String updateAutocryptPeerStateFromIntent(Intent data, AutocryptPeerDataAccessObject autocryptPeerDao) private String updateAutocryptPeerStateFromIntent(Intent data, AutocryptPeerDataAccessObject autocryptPeerDao)
throws PgpGeneralException, IOException { throws PgpGeneralException, IOException {
String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID);
AutocryptPeerUpdate inlineKeyUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_INLINE_KEY_DATA); AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE);
if (inlineKeyUpdate == null) { if (autocryptPeerUpdate == null) {
return null; return null;
} }
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(inlineKeyUpdate.getKeyData()); Long newMasterKeyId;
if (uncachedKeyRing.isSecret()) { if (autocryptPeerUpdate.hasKeyData()) {
Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring"); UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData());
return null; if (uncachedKeyRing.isSecret()) {
} Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring");
// this will merge if the key already exists - no worries! return null;
KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing); }
long inlineMasterKeyId = uncachedKeyRing.getMasterKeyId(); // this will merge if the key already exists - no worries!
KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing);
Date lastUpdate = autocryptPeerDao.getLastUpdateForAutocryptPeer(autocryptPeerId); newMasterKeyId = uncachedKeyRing.getMasterKeyId();
Date updateTimestamp = inlineKeyUpdate.getTimestamp();
Long autocryptMasterKeyId = autocryptPeerDao.getMasterKeyIdForAutocryptPeer(autocryptPeerId);
if (lastUpdate != null && lastUpdate.after(updateTimestamp)) {
Log.d(Constants.TAG, "Key for autocrypt peer is newer, ignoring other");
return autocryptPeerId;
} else if (autocryptMasterKeyId == null) {
Log.d(Constants.TAG, "No binding for autocrypt peer, pinning key");
autocryptPeerDao.setMasterKeyIdForAutocryptPeer(autocryptPeerId, inlineMasterKeyId, updateTimestamp);
} else if (inlineMasterKeyId == autocryptMasterKeyId) {
Log.d(Constants.TAG, "Key id is the same - doing nothing");
} else { } else {
// TODO danger in result intent! newMasterKeyId = null;
autocryptPeerDao.setMasterKeyIdForAutocryptPeer(autocryptPeerId, inlineMasterKeyId, updateTimestamp); }
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; return autocryptPeerId;
@@ -748,43 +760,17 @@ public class OpenPgpService extends Service {
private Intent updateAutocryptPeerImpl(Intent data) { private Intent updateAutocryptPeerImpl(Intent data) {
try { try {
Intent result = new Intent(); if (!data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) ||
!data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) {
String autocryptPeer = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); throw new IllegalArgumentException("need to specify both autocrypt_peer_id and autocrypt_peer_update!");
AutocryptPeerUpdate inlineKeyUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_INLINE_KEY_DATA);
if (inlineKeyUpdate == null || autocryptPeer == null) {
throw new IllegalArgumentException("need to specify both autocrypt_peer_id and inline_key_data!");
} }
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(inlineKeyUpdate.getKeyData());
long inlineMasterKeyId = uncachedKeyRing.getMasterKeyId();
// this will merge if the key already exists - no worries!
KeyWritableRepository.createDatabaseReadWriteInteractor(this).savePublicKeyRing(uncachedKeyRing);
AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(), AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(),
mApiPermissionHelper.getCurrentCallingPackage()); mApiPermissionHelper.getCurrentCallingPackage());
Date lastUpdate = autocryptPeerentityDao.getLastUpdateForAutocryptPeer(autocryptPeer); updateAutocryptPeerStateFromIntent(data, autocryptPeerentityDao);
Date updateTimestamp = inlineKeyUpdate.getTimestamp();
boolean updateIsNewerThanLastUpdate = lastUpdate == null || lastUpdate.before(updateTimestamp);
if (!updateIsNewerThanLastUpdate) {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
}
Log.d(Constants.TAG, "Key for autocrypt peer is newer");
Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeer);
if (autocryptPeerMasterKeyId == null) {
Log.d(Constants.TAG, "No binding for autocrypt peer, pinning key");
autocryptPeerentityDao.setMasterKeyIdForAutocryptPeer(autocryptPeer, inlineMasterKeyId, updateTimestamp);
} else if (inlineMasterKeyId == autocryptPeerMasterKeyId) {
Log.d(Constants.TAG, "Key id is the same - doing nothing");
} else {
// TODO danger in result intent!
autocryptPeerentityDao.setMasterKeyIdForAutocryptPeer(autocryptPeer, inlineMasterKeyId, updateTimestamp);
}
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result; return result;
} catch (Exception e) { } catch (Exception e) {
@@ -940,11 +926,13 @@ public class OpenPgpService extends Service {
case OpenPgpApi.ACTION_DETACHED_SIGN: { case OpenPgpApi.ACTION_DETACHED_SIGN: {
return signImpl(data, inputStream, outputStream, false); return signImpl(data, inputStream, outputStream, false);
} }
case OpenPgpApi.ACTION_ENCRYPT: { case OpenPgpApi.ACTION_QUERY_AUTOCRYPT_STATUS: {
return encryptAndSignImpl(data, inputStream, outputStream, false); return encryptAndSignImpl(data, inputStream, outputStream, false, true);
} }
case OpenPgpApi.ACTION_ENCRYPT:
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: { case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
return encryptAndSignImpl(data, inputStream, outputStream, true); boolean enableSign = action.equals(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
return encryptAndSignImpl(data, inputStream, outputStream, enableSign, false);
} }
case OpenPgpApi.ACTION_DECRYPT_VERIFY: { case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
return decryptAndVerifyImpl(data, inputStream, outputStream, false, progressable); return decryptAndVerifyImpl(data, inputStream, outputStream, false, progressable);

View File

@@ -17,6 +17,7 @@ import android.support.annotation.VisibleForTesting;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptPeerStatus;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@@ -24,14 +25,23 @@ import org.sufficientlysecure.keychain.util.Log;
class OpenPgpServiceKeyIdExtractor { class OpenPgpServiceKeyIdExtractor {
@VisibleForTesting @VisibleForTesting
static final String[] PROJECTION_KEY_SEARCH = { static final String[] PROJECTION_MAIL_STATUS = {
"email_address", EmailStatus.EMAIL_ADDRESS,
"master_key_id", EmailStatus.MASTER_KEY_ID,
"email_status", EmailStatus.USER_ID_STATUS,
}; };
private static final int INDEX_EMAIL_ADDRESS = 0; private static final int INDEX_EMAIL_ADDRESS = 0;
private static final int INDEX_MASTER_KEY_ID = 1; private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_EMAIL_STATUS = 2; 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 final ApiPendingIntentFactory apiPendingIntentFactory; private final ApiPendingIntentFactory apiPendingIntentFactory;
@@ -88,6 +98,8 @@ class OpenPgpServiceKeyIdExtractor {
if (hasAddresses) { if (hasAddresses) {
HashMap<String, UserIdStatus> keyRows = getStatusMapForQueriedAddresses(encryptionAddresses, callingPackageName); HashMap<String, UserIdStatus> keyRows = getStatusMapForQueriedAddresses(encryptionAddresses, callingPackageName);
HashMap<String, AutocryptRecommendation> autocryptRows = getAutocryptRecommendationsForQueriedAddresses(
encryptionAddresses, callingPackageName);
boolean anyKeyNotVerified = false; boolean anyKeyNotVerified = false;
for (Entry<String, UserIdStatus> entry : keyRows.entrySet()) { for (Entry<String, UserIdStatus> entry : keyRows.entrySet()) {
@@ -105,7 +117,8 @@ class OpenPgpServiceKeyIdExtractor {
duplicateEmails.add(queriedAddress); duplicateEmails.add(queriedAddress);
} }
if (!userIdStatus.verified) { if (userIdStatus.userIdVerified != KeychainExternalContract.KEY_STATUS_VERIFIED &&
userIdStatus.autocryptPeerVerified != KeychainExternalContract.KEY_STATUS_VERIFIED) {
anyKeyNotVerified = true; anyKeyNotVerified = true;
} }
} }
@@ -141,7 +154,7 @@ class OpenPgpServiceKeyIdExtractor {
private HashMap<String, UserIdStatus> getStatusMapForQueriedAddresses(String[] encryptionUserIds, String callingPackageName) { private HashMap<String, UserIdStatus> getStatusMapForQueriedAddresses(String[] encryptionUserIds, String callingPackageName) {
HashMap<String,UserIdStatus> keyRows = new HashMap<>(); HashMap<String,UserIdStatus> keyRows = new HashMap<>();
Uri queryUri = EmailStatus.CONTENT_URI.buildUpon().appendPath(callingPackageName).build(); Uri queryUri = EmailStatus.CONTENT_URI.buildUpon().appendPath(callingPackageName).build();
Cursor cursor = contentResolver.query(queryUri, PROJECTION_KEY_SEARCH, null, encryptionUserIds, null); Cursor cursor = contentResolver.query(queryUri, PROJECTION_MAIL_STATUS, null, encryptionUserIds, null);
if (cursor == null) { if (cursor == null) {
throw new IllegalStateException("Internal error, received null cursor!"); throw new IllegalStateException("Internal error, received null cursor!");
} }
@@ -150,21 +163,33 @@ class OpenPgpServiceKeyIdExtractor {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
String queryAddress = cursor.getString(INDEX_EMAIL_ADDRESS); String queryAddress = cursor.getString(INDEX_EMAIL_ADDRESS);
Long masterKeyId = cursor.isNull(INDEX_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_MASTER_KEY_ID); Long masterKeyId = cursor.isNull(INDEX_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_MASTER_KEY_ID);
boolean isVerified = cursor.getInt(INDEX_EMAIL_STATUS) == KeychainExternalContract.KEY_STATUS_VERIFIED; int userIdStatus = cursor.getInt(INDEX_USER_ID_STATUS);
UserIdStatus userIdStatus = new UserIdStatus(masterKeyId, isVerified);
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);
boolean seenBefore = keyRows.containsKey(queryAddress); boolean seenBefore = keyRows.containsKey(queryAddress);
if (!seenBefore) { if (!seenBefore) {
keyRows.put(queryAddress, userIdStatus); keyRows.put(queryAddress, status);
continue; continue;
} }
UserIdStatus previousUserIdStatus = keyRows.get(queryAddress); UserIdStatus previousUserIdStatus = keyRows.get(queryAddress);
if (previousUserIdStatus.autocryptPeerVerified != KeychainExternalContract.KEY_STATUS_UNAVAILABLE) {
continue;
}
if (previousUserIdStatus.masterKeyId == null) { if (previousUserIdStatus.masterKeyId == null) {
keyRows.put(queryAddress, userIdStatus); keyRows.put(queryAddress, status);
} else if (!previousUserIdStatus.verified && userIdStatus.verified) { } else if (previousUserIdStatus.userIdVerified < status.userIdVerified) {
keyRows.put(queryAddress, userIdStatus); keyRows.put(queryAddress, status);
} else if (previousUserIdStatus.verified == userIdStatus.verified) { } else if (previousUserIdStatus.userIdVerified == status.userIdVerified) {
previousUserIdStatus.hasDuplicate = true; previousUserIdStatus.hasDuplicate = true;
} }
} }
@@ -174,14 +199,50 @@ class OpenPgpServiceKeyIdExtractor {
return keyRows; 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!");
}
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();
}
return keyRows;
}
private static class UserIdStatus { private static class UserIdStatus {
private final Long masterKeyId; private final Long masterKeyId;
private final boolean verified; private final int userIdVerified;
private boolean hasDuplicate; private boolean hasDuplicate;
UserIdStatus(Long masterKeyId, boolean verified) { UserIdStatus(Long masterKeyId, int userIdVerified) {
this.masterKeyId = masterKeyId; this.masterKeyId = masterKeyId;
this.verified = verified; this.userIdVerified = userIdVerified;
} }
} }
@@ -256,6 +317,10 @@ class OpenPgpServiceKeyIdExtractor {
} }
} }
enum AutocryptRecommendation {
UNAVAILABLE, DISCOURAGE, AVAILABLE, MUTUAL
}
enum KeyIdResultStatus { enum KeyIdResultStatus {
OK, MISSING, DUPLICATE, NO_KEYS, NO_KEYS_ERROR OK, MISSING, DUPLICATE, NO_KEYS, NO_KEYS_ERROR
} }

View File

@@ -187,7 +187,7 @@ public class KeychainExternalProviderTest {
Cursor cursor = contentResolver.query( Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] { EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID,
EmailStatus.AUTOCRYPT_PEER_STATE }, EmailStatus.AUTOCRYPT_PEER_STATUS },
null, new String [] { AUTOCRYPT_PEER }, null null, new String [] { AUTOCRYPT_PEER }, null
); );
@@ -211,7 +211,7 @@ public class KeychainExternalProviderTest {
Cursor cursor = contentResolver.query( Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] { EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID,
EmailStatus.AUTOCRYPT_PEER_STATE }, EmailStatus.AUTOCRYPT_PEER_STATUS },
null, new String [] { AUTOCRYPT_PEER }, null null, new String [] { AUTOCRYPT_PEER }, null
); );

View File

@@ -205,14 +205,14 @@ public class OpenPgpServiceKeyIdExtractorTest {
} }
private void setupContentResolverResult() { private void setupContentResolverResult() {
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_KEY_SEARCH); MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_MAIL_STATUS);
when(contentResolver.query( when(contentResolver.query(
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class))) any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
.thenReturn(resultCursor); .thenReturn(resultCursor);
} }
private void setupContentResolverResult(String[] userIds, Long[] resultKeyIds, int[] verified) { private void setupContentResolverResult(String[] userIds, Long[] resultKeyIds, int[] verified) {
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_KEY_SEARCH); MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_MAIL_STATUS);
for (int i = 0; i < userIds.length; i++) { for (int i = 0; i < userIds.length; i++) {
resultCursor.addRow(new Object[] { userIds[i], resultKeyIds[i], verified[i] }); resultCursor.addRow(new Object[] { userIds[i], resultKeyIds[i], verified[i] });
} }