extract autocrypt_peers from KeychainProvider into AutocryptPeerDao
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package org.sufficientlysecure.keychain.model;
|
||||
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.squareup.sqldelight.RowMapper;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel;
|
||||
|
||||
|
||||
@AutoValue
|
||||
public abstract class AutocryptPeer implements AutocryptPeersModel {
|
||||
|
||||
public enum GossipOrigin {
|
||||
GOSSIP_HEADER, SIGNATURE, DEDUP
|
||||
}
|
||||
|
||||
public static final Factory<AutocryptPeer> FACTORY = new Factory<AutocryptPeer>(AutoValue_AutocryptPeer::new,
|
||||
CustomColumnAdapters.DATE_ADAPTER, CustomColumnAdapters.DATE_ADAPTER, CustomColumnAdapters.DATE_ADAPTER,
|
||||
CustomColumnAdapters.GOSSIP_ORIGIN_ADAPTER);
|
||||
|
||||
public static final RowMapper<AutocryptPeer> PEER_MAPPER = FACTORY.selectByIdentifiersMapper();
|
||||
public static final RowMapper<AutocryptKeyStatus> KEY_STATUS_MAPPER =
|
||||
FACTORY.selectAutocryptKeyStatusMapper(AutoValue_AutocryptPeer_AutocryptKeyStatus::new);
|
||||
|
||||
@AutoValue
|
||||
public static abstract class AutocryptKeyStatus implements SelectAutocryptKeyStatusModel<AutocryptPeer> {
|
||||
public boolean hasGossipKey() {
|
||||
return autocryptPeer().gossip_master_key_id() != null;
|
||||
}
|
||||
|
||||
public boolean isGossipKeyRevoked() {
|
||||
Long revokedInt = gossip_key_is_revoked_int();
|
||||
return revokedInt != null && revokedInt != 0;
|
||||
}
|
||||
|
||||
public boolean isGossipKeyExpired() {
|
||||
return gossip_key_is_expired_int() != 0;
|
||||
}
|
||||
|
||||
public boolean isGossipKeyVerified() {
|
||||
return gossip_key_is_verified_int() != 0;
|
||||
}
|
||||
|
||||
public boolean hasKey() {
|
||||
return autocryptPeer().master_key_id() != null;
|
||||
}
|
||||
|
||||
public boolean isKeyRevoked() {
|
||||
Long revokedInt = key_is_revoked_int();
|
||||
return revokedInt != null && revokedInt != 0;
|
||||
}
|
||||
|
||||
public boolean isKeyExpired() {
|
||||
return key_is_expired_int() != 0;
|
||||
}
|
||||
|
||||
public boolean isKeyVerified() {
|
||||
return key_is_verified_int() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import java.util.Date;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.squareup.sqldelight.ColumnAdapter;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
|
||||
|
||||
|
||||
public final class CustomColumnAdapters {
|
||||
@@ -25,4 +27,27 @@ public final class CustomColumnAdapters {
|
||||
return value.getTime() / 1000;
|
||||
}
|
||||
};
|
||||
|
||||
static final ColumnAdapter<GossipOrigin,Long> GOSSIP_ORIGIN_ADAPTER = new ColumnAdapter<GossipOrigin,Long>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public GossipOrigin decode(Long databaseValue) {
|
||||
switch (databaseValue.intValue()) {
|
||||
case 0: return GossipOrigin.GOSSIP_HEADER;
|
||||
case 10: return GossipOrigin.SIGNATURE;
|
||||
case 20: return GossipOrigin.DEDUP;
|
||||
default: throw new IllegalArgumentException("Unhandled database value!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long encode(@NonNull GossipOrigin value) {
|
||||
switch (value) {
|
||||
case GOSSIP_HEADER: return 0L;
|
||||
case SIGNATURE: return 10L;
|
||||
case DEDUP: return 20L;
|
||||
default: throw new IllegalArgumentException("Unhandled database value!");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,8 +54,12 @@ public class ApiDataAccessObject {
|
||||
}
|
||||
|
||||
public byte[] getApiAppCertificate(String packageName) {
|
||||
Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName));
|
||||
return ApiApp.FACTORY.getCertificateMapper().map(cursor);
|
||||
try (Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName))) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return ApiApp.FACTORY.getCertificateMapper().map(cursor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void insertApiApp(ApiApp apiApp) {
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByIdentifier;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByMasterKeyId;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.InsertPeer;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateGossipKey;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateKey;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateLastSeen;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
|
||||
|
||||
public class AutocryptPeerDao {
|
||||
private final SupportSQLiteDatabase db;
|
||||
private final DatabaseNotifyManager databaseNotifyManager;
|
||||
|
||||
public static AutocryptPeerDao getInstance(Context context) {
|
||||
KeychainDatabase keychainDatabase = new KeychainDatabase(context);
|
||||
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
|
||||
return new AutocryptPeerDao(keychainDatabase.getWritableDatabase(), databaseNotifyManager);
|
||||
}
|
||||
|
||||
private AutocryptPeerDao(SupportSQLiteDatabase writableDatabase, DatabaseNotifyManager databaseNotifyManager) {
|
||||
this.db = writableDatabase;
|
||||
this.databaseNotifyManager = databaseNotifyManager;
|
||||
}
|
||||
|
||||
public Long getMasterKeyIdForAutocryptPeer(String autocryptId) {
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifier(autocryptId);
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifierMapper().map(cursor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AutocryptPeer getAutocryptPeer(String packageName, String autocryptId) {
|
||||
List<AutocryptPeer> autocryptPeers = getAutocryptPeers(packageName, autocryptId);
|
||||
if (!autocryptPeers.isEmpty()) {
|
||||
return autocryptPeers.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<AutocryptPeer> getAutocryptPeers(String packageName, String... autocryptId) {
|
||||
ArrayList<AutocryptPeer> result = new ArrayList<>(autocryptId.length);
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectByIdentifiers(packageName, autocryptId);
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor);
|
||||
result.add(autocryptPeer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<AutocryptKeyStatus> getAutocryptKeyStatus(String packageName, String[] autocryptIds) {
|
||||
ArrayList<AutocryptKeyStatus> result = new ArrayList<>(autocryptIds.length);
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectAutocryptKeyStatus(packageName, autocryptIds, System.currentTimeMillis());
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
AutocryptKeyStatus autocryptPeer = AutocryptPeer.KEY_STATUS_MAPPER.map(cursor);
|
||||
result.add(autocryptPeer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void insertOrUpdateLastSeen(String packageName, String autocryptId, Date date) {
|
||||
UpdateLastSeen updateStatement = new UpdateLastSeen(db, AutocryptPeer.FACTORY);
|
||||
updateStatement.bind(packageName, autocryptId, date);
|
||||
int updated = updateStatement.executeUpdateDelete();
|
||||
|
||||
if (updated == 0) {
|
||||
InsertPeer insertStatement = new InsertPeer(db, AutocryptPeer.FACTORY);
|
||||
insertStatement.bind(packageName, autocryptId, date);
|
||||
insertStatement.executeInsert();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateKey(String packageName, String autocryptId, Date effectiveDate, long masterKeyId,
|
||||
boolean isMutual) {
|
||||
UpdateKey updateStatement = new UpdateKey(db, AutocryptPeer.FACTORY);
|
||||
updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, isMutual);
|
||||
int rowsUpdated = updateStatement.executeUpdateDelete();
|
||||
if (rowsUpdated == 0) {
|
||||
throw new IllegalStateException("No rows updated! Was this peer inserted before the update?");
|
||||
}
|
||||
databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
public void updateKeyGossip(String packageName, String autocryptId, Date effectiveDate, long masterKeyId,
|
||||
GossipOrigin origin) {
|
||||
UpdateGossipKey updateStatement = new UpdateGossipKey(db, AutocryptPeer.FACTORY);
|
||||
updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, origin);
|
||||
int rowsUpdated = updateStatement.executeUpdateDelete();
|
||||
if (rowsUpdated == 0) {
|
||||
throw new IllegalStateException("No rows updated! Was this peer inserted before the update?");
|
||||
}
|
||||
databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
public List<AutocryptPeer> getAutocryptPeersForKey(long masterKeyId) {
|
||||
ArrayList<AutocryptPeer> result = new ArrayList<>();
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectByMasterKeyId(masterKeyId);
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor);
|
||||
result.add(autocryptPeer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void deleteByIdentifier(String packageName, String autocryptId) {
|
||||
Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId);
|
||||
DeleteByIdentifier deleteStatement = new DeleteByIdentifier(db);
|
||||
deleteStatement.bind(packageName, autocryptId);
|
||||
deleteStatement.execute();
|
||||
if (masterKeyId != null) {
|
||||
databaseNotifyManager.notifyAutocryptDelete(autocryptId, masterKeyId);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteByMasterKeyId(long masterKeyId) {
|
||||
DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db);
|
||||
deleteStatement.bind(masterKeyId);
|
||||
deleteStatement.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS;
|
||||
|
||||
private static final String[] PROJECTION_LAST_SEEN = { ApiAutocryptPeer.LAST_SEEN };
|
||||
private static final String[] PROJECTION_LAST_SEEN_KEY = { ApiAutocryptPeer.LAST_SEEN_KEY };
|
||||
private static final String[] PROJECTION_GOSSIP_LAST_SEEN_KEY = { ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY };
|
||||
private static final String[] PROJECTION_MASTER_KEY_ID = { ApiAutocryptPeer.MASTER_KEY_ID };
|
||||
|
||||
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;
|
||||
private final DatabaseNotifyManager databaseNotifyManager;
|
||||
|
||||
public AutocryptPeerDataAccessObject(Context context, String packageName) {
|
||||
this.packageName = packageName;
|
||||
this.databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
|
||||
final ContentResolver contentResolver = context.getContentResolver();
|
||||
queryInterface = new SimpleContentResolverInterface() {
|
||||
@Override
|
||||
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri contentUri, ContentValues values) {
|
||||
return contentResolver.insert(contentUri, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) {
|
||||
return contentResolver.update(contentUri, values, where, selectionArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri contentUri, String where, String[] selectionArgs) {
|
||||
return contentResolver.delete(contentUri, where, selectionArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public AutocryptPeerDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName,
|
||||
DatabaseNotifyManager databaseNotifyManager) {
|
||||
this.queryInterface = queryInterface;
|
||||
this.packageName = packageName;
|
||||
this.databaseNotifyManager = databaseNotifyManager;
|
||||
}
|
||||
|
||||
public Long getMasterKeyIdForAutocryptPeer(String autocryptId) {
|
||||
Cursor cursor = queryInterface.query(
|
||||
ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
|
||||
PROJECTION_MASTER_KEY_ID, null, null, null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getLong(0);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Date getLastSeen(String autocryptId) {
|
||||
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
|
||||
PROJECTION_LAST_SEEN, null, null, null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long lastUpdated = cursor.getLong(0);
|
||||
return new Date(lastUpdated);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Date getLastSeenKey(String autocryptId) {
|
||||
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
|
||||
PROJECTION_LAST_SEEN_KEY, null, null, null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long lastUpdated = cursor.getLong(0);
|
||||
return new Date(lastUpdated);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Date getLastSeenGossip(String autocryptId) {
|
||||
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
|
||||
PROJECTION_GOSSIP_LAST_SEEN_KEY, null, null, null);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long lastUpdated = cursor.getLong(0);
|
||||
return new Date(lastUpdated);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updateLastSeen(String autocryptId, Date date) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime());
|
||||
queryInterface
|
||||
.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
|
||||
}
|
||||
|
||||
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_KEY, effectiveDate.getTime());
|
||||
cv.put(ApiAutocryptPeer.IS_MUTUAL, isMutual ? 1 : 0);
|
||||
queryInterface
|
||||
.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
|
||||
databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
public void updateKeyGossipFromAutocrypt(String autocryptId, Date effectiveDate, long masterKeyId) {
|
||||
updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_AUTOCRYPT);
|
||||
}
|
||||
|
||||
public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) {
|
||||
updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_SIGNATURE);
|
||||
}
|
||||
|
||||
public void updateKeyGossipFromDedup(String autocryptId, Date effectiveDate, long masterKeyId) {
|
||||
updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_DEDUP);
|
||||
}
|
||||
|
||||
private void updateKeyGossip(String autocryptId, Date effectiveDate, long masterKeyId, int origin) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, masterKeyId);
|
||||
cv.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, effectiveDate.getTime());
|
||||
cv.put(ApiAutocryptPeer.GOSSIP_ORIGIN, origin);
|
||||
queryInterface
|
||||
.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
|
||||
databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
public void delete(String autocryptId) {
|
||||
Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId);
|
||||
queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null);
|
||||
databaseNotifyManager.notifyAutocryptDelete(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
@@ -84,29 +83,34 @@ public class KeyWritableRepository extends KeyRepository {
|
||||
|
||||
private final Context context;
|
||||
private final DatabaseNotifyManager databaseNotifyManager;
|
||||
private AutocryptPeerDao autocryptPeerDao;
|
||||
|
||||
public static KeyWritableRepository create(Context context) {
|
||||
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
|
||||
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
|
||||
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager);
|
||||
AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context);
|
||||
return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager,
|
||||
autocryptPeerDao);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
KeyWritableRepository(Context context,
|
||||
LocalPublicKeyStorage localPublicKeyStorage,
|
||||
LocalSecretKeyStorage localSecretKeyStorage,
|
||||
DatabaseNotifyManager databaseNotifyManager) {
|
||||
this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0);
|
||||
DatabaseNotifyManager databaseNotifyManager, AutocryptPeerDao autocryptPeerDao) {
|
||||
this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0,
|
||||
autocryptPeerDao);
|
||||
}
|
||||
|
||||
private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage,
|
||||
LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager,
|
||||
OperationLog log, int indent) {
|
||||
OperationLog log, int indent, AutocryptPeerDao autocryptPeerDao) {
|
||||
super(context.getContentResolver(), localPublicKeyStorage, localSecretKeyStorage, log, indent);
|
||||
|
||||
this.context = context;
|
||||
this.databaseNotifyManager = databaseNotifyManager;
|
||||
this.autocryptPeerDao = autocryptPeerDao;
|
||||
}
|
||||
|
||||
private LongSparseArray<CanonicalizedPublicKey> getTrustedMasterKeys() {
|
||||
@@ -585,7 +589,7 @@ public class KeyWritableRepository extends KeyRepository {
|
||||
Timber.e(e, "Could not delete file!");
|
||||
return false;
|
||||
}
|
||||
contentResolver.delete(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId),null, null);
|
||||
autocryptPeerDao.deleteByMasterKeyId(masterKeyId);
|
||||
int deletedRows = contentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null);
|
||||
|
||||
databaseNotifyManager.notifyKeyChange(masterKeyId);
|
||||
|
||||
@@ -51,13 +51,6 @@ public class KeychainContract {
|
||||
String EXPIRY = "expiry";
|
||||
}
|
||||
|
||||
interface UpdatedKeysColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String LAST_UPDATED = "last_updated"; // time since epoch in seconds
|
||||
String SEEN_ON_KEYSERVERS = "seen_on_keyservers";
|
||||
String FINGERPRINT = "fingerprint";
|
||||
}
|
||||
|
||||
interface KeySignaturesColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String SIGNER_KEY_ID = "signer_key_id";
|
||||
@@ -86,11 +79,6 @@ public class KeychainContract {
|
||||
String DATA = "data";
|
||||
}
|
||||
|
||||
interface ApiAppsColumns {
|
||||
String PACKAGE_NAME = "package_name";
|
||||
String PACKAGE_CERTIFICATE = "package_signature";
|
||||
}
|
||||
|
||||
interface ApiAppsAllowedKeysColumns {
|
||||
String KEY_ID = "key_id"; // not a database id
|
||||
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||
@@ -100,20 +88,6 @@ public class KeychainContract {
|
||||
String IDENTIFIER = "identifier";
|
||||
}
|
||||
|
||||
interface ApiAutocryptPeerColumns {
|
||||
String PACKAGE_NAME = "package_name";
|
||||
String IDENTIFIER = "identifier";
|
||||
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;
|
||||
|
||||
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
|
||||
@@ -121,8 +95,6 @@ public class KeychainContract {
|
||||
|
||||
public static final String BASE_KEY_RINGS = "key_rings";
|
||||
|
||||
public static final String BASE_UPDATED_KEYS = "updated_keys";
|
||||
|
||||
public static final String BASE_KEY_SIGNATURES = "key_signatures";
|
||||
|
||||
public static final String PATH_UNIFIED = "unified";
|
||||
@@ -141,11 +113,6 @@ public class KeychainContract {
|
||||
public static final String PATH_KEYS = "keys";
|
||||
public static final String PATH_CERTS = "certs";
|
||||
|
||||
public static final String PATH_BY_PACKAGE_NAME = "by_package_name";
|
||||
public static final String PATH_BY_KEY_ID = "by_key_id";
|
||||
|
||||
public static final String BASE_AUTOCRYPT_PEERS = "autocrypt_peers";
|
||||
|
||||
public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
|
||||
public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
|
||||
public static final String IS_REVOKED = KeysColumns.IS_REVOKED;
|
||||
@@ -310,37 +277,6 @@ 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 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();
|
||||
}
|
||||
|
||||
public static Uri buildByMasterKeyId(long masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(Long.toString(masterKeyId)).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Certs implements CertsColumns, BaseColumns {
|
||||
public static final String USER_ID = UserPacketsColumns.USER_ID;
|
||||
public static final String NAME = UserPacketsColumns.NAME;
|
||||
|
||||
@@ -30,21 +30,21 @@ import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
|
||||
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.KeyMetadataModel;
|
||||
import org.sufficientlysecure.keychain.KeyRingsPublicModel;
|
||||
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignaturesColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import timber.log.Timber;
|
||||
@@ -60,20 +60,18 @@ import timber.log.Timber;
|
||||
*/
|
||||
public class KeychainDatabase {
|
||||
private static final String DATABASE_NAME = "openkeychain.db";
|
||||
private static final int DATABASE_VERSION = 27;
|
||||
private static final int DATABASE_VERSION = 28;
|
||||
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
|
||||
private Context context;
|
||||
|
||||
public interface Tables {
|
||||
String KEY_RINGS_PUBLIC = "keyrings_public";
|
||||
String KEYS = "keys";
|
||||
String UPDATED_KEYS = "updated_keys";
|
||||
String KEY_SIGNATURES = "key_signatures";
|
||||
String USER_PACKETS = "user_packets";
|
||||
String CERTS = "certs";
|
||||
String API_ALLOWED_KEYS = "api_allowed_keys";
|
||||
String OVERRIDDEN_WARNINGS = "overridden_warnings";
|
||||
String API_AUTOCRYPT_PEERS = "api_autocrypt_peers";
|
||||
}
|
||||
|
||||
private static final String CREATE_KEYS =
|
||||
@@ -151,23 +149,6 @@ public class KeychainDatabase {
|
||||
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_API_AUTOCRYPT_PEERS =
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.API_AUTOCRYPT_PEERS + " ("
|
||||
+ ApiAutocryptPeerColumns.PACKAGE_NAME + " TEXT NOT NULL, "
|
||||
+ ApiAutocryptPeerColumns.IDENTIFIER + " TEXT 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 "
|
||||
+ "api_apps (package_signature) ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_API_APPS_ALLOWED_KEYS =
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " ("
|
||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
@@ -237,7 +218,7 @@ public class KeychainDatabase {
|
||||
db.execSQL(CREATE_KEY_SIGNATURES);
|
||||
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
||||
db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
|
||||
db.execSQL(CREATE_API_AUTOCRYPT_PEERS);
|
||||
db.execSQL(AutocryptPeersModel.CREATE_TABLE);
|
||||
db.execSQL(ApiApp.CREATE_TABLE);
|
||||
|
||||
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");");
|
||||
@@ -466,6 +447,10 @@ public class KeychainDatabase {
|
||||
case 26: {
|
||||
migrateUpdatedKeysToKeyMetadataTable(db);
|
||||
}
|
||||
|
||||
case 27: {
|
||||
renameApiAutocryptPeersTable(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +473,10 @@ public class KeychainDatabase {
|
||||
db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;");
|
||||
}
|
||||
|
||||
private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) {
|
||||
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;");
|
||||
}
|
||||
|
||||
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// Downgrade is ok for the debug version, makes it easier to work with branches
|
||||
if (Constants.DEBUG) {
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
|
||||
|
||||
|
||||
public class KeychainExternalContract {
|
||||
|
||||
@@ -38,8 +38,8 @@ import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
@@ -74,10 +74,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
private static final int KEY_RINGS_FIND_BY_USER_ID = 402;
|
||||
private static final int KEY_RINGS_FILTER_BY_SIGNER = 403;
|
||||
|
||||
private static final int AUTOCRYPT_PEERS_BY_MASTER_KEY_ID = 601;
|
||||
private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME = 602;
|
||||
private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID = 603;
|
||||
|
||||
private static final int KEY_SIGNATURES = 700;
|
||||
|
||||
protected UriMatcher mUriMatcher;
|
||||
@@ -173,21 +169,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
+ KeychainContract.PATH_CERTS + "/*/*",
|
||||
KEY_RING_CERTS_SPECIFIC);
|
||||
|
||||
/*
|
||||
* Trust Identity access
|
||||
*
|
||||
* <pre>
|
||||
* trust_ids/by_key_id/_
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" +
|
||||
KeychainContract.PATH_BY_KEY_ID + "/*", AUTOCRYPT_PEERS_BY_MASTER_KEY_ID);
|
||||
matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" +
|
||||
KeychainContract.PATH_BY_PACKAGE_NAME + "/*", AUTOCRYPT_PEERS_BY_PACKAGE_NAME);
|
||||
matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" +
|
||||
KeychainContract.PATH_BY_PACKAGE_NAME + "/*/*", AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID);
|
||||
|
||||
matcher.addURI(authority, KeychainContract.BASE_KEY_SIGNATURES, KEY_SIGNATURES);
|
||||
|
||||
|
||||
@@ -309,7 +290,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
"(" + 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(DISTINCT aTI." + ApiAutocryptPeer.PACKAGE_NAME + ") AS "
|
||||
"GROUP_CONCAT(DISTINCT aTI." + AutocryptPeer.PACKAGE_NAME + ") AS "
|
||||
+ KeyRings.API_KNOWN_TO_PACKAGE_NAMES);
|
||||
qb.setProjectionMap(projectionMap);
|
||||
|
||||
@@ -390,8 +371,8 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
+ " >= " + new Date().getTime() / 1000 + " )"
|
||||
+ ")" : "")
|
||||
+ (plist.contains(KeyRings.API_KNOWN_TO_PACKAGE_NAMES) ?
|
||||
" LEFT JOIN " + Tables.API_AUTOCRYPT_PEERS + " AS aTI ON ("
|
||||
+"aTI." + Keys.MASTER_KEY_ID
|
||||
" LEFT JOIN " + AutocryptPeer.TABLE_NAME + " AS aTI ON ("
|
||||
+"aTI." + AutocryptPeer.MASTER_KEY_ID
|
||||
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||
+ ")" : "")
|
||||
);
|
||||
@@ -639,76 +620,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
break;
|
||||
}
|
||||
|
||||
case AUTOCRYPT_PEERS_BY_MASTER_KEY_ID:
|
||||
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME:
|
||||
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
|
||||
long unixSeconds = new Date().getTime() / 1000;
|
||||
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(ApiAutocryptPeer._ID, Tables.API_AUTOCRYPT_PEERS + ".oid AS " + ApiAutocryptPeer._ID);
|
||||
projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER);
|
||||
projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME);
|
||||
projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN);
|
||||
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 +
|
||||
" 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());
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
selection = Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = ? AND " +
|
||||
Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.IDENTIFIER + " = ?";
|
||||
selectionArgs = new String[] { packageName, autocryptPeer };
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
|
||||
}
|
||||
@@ -846,25 +757,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
break;
|
||||
}
|
||||
|
||||
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
|
||||
String packageName = uri.getPathSegments().get(2);
|
||||
String autocryptPeer = uri.getPathSegments().get(3);
|
||||
|
||||
String selection = ApiAutocryptPeer.PACKAGE_NAME + " = ? AND " + ApiAutocryptPeer.IDENTIFIER + " = ?";
|
||||
selectionArgs = new String[] { packageName, autocryptPeer };
|
||||
|
||||
count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUTOCRYPT_PEERS_BY_MASTER_KEY_ID:
|
||||
String selection = ApiAutocryptPeer.MASTER_KEY_ID + " = " + uri.getLastPathSegment();
|
||||
if (!TextUtils.isEmpty(additionalSelection)) {
|
||||
selection += " AND (" + additionalSelection + ")";
|
||||
}
|
||||
count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
|
||||
break;
|
||||
|
||||
default: {
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
@@ -900,21 +792,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
||||
count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs);
|
||||
break;
|
||||
}
|
||||
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
|
||||
String packageName = uri.getPathSegments().get(2);
|
||||
String identifier = uri.getLastPathSegment();
|
||||
values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName);
|
||||
values.put(ApiAutocryptPeer.IDENTIFIER, identifier);
|
||||
|
||||
int updated = db.update(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_IGNORE, values,
|
||||
ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?",
|
||||
new String[] { packageName, identifier });
|
||||
if (updated == 0) {
|
||||
db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
|
||||
@@ -2,52 +2,64 @@ package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import org.openintents.openpgp.AutocryptPeerUpdate;
|
||||
import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
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.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
class AutocryptInteractor {
|
||||
public class AutocryptInteractor {
|
||||
private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS;
|
||||
|
||||
private AutocryptPeerDataAccessObject autocryptPeerDao;
|
||||
private AutocryptPeerDao autocryptPeerDao;
|
||||
private KeyWritableRepository keyWritableRepository;
|
||||
|
||||
public static AutocryptInteractor getInstance(Context context, AutocryptPeerDataAccessObject autocryptPeerentityDao) {
|
||||
private final String packageName;
|
||||
|
||||
public static AutocryptInteractor getInstance(Context context, String packageName) {
|
||||
AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context);
|
||||
KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(context);
|
||||
|
||||
return new AutocryptInteractor(autocryptPeerentityDao, keyWritableRepository);
|
||||
return new AutocryptInteractor(autocryptPeerDao, keyWritableRepository, packageName);
|
||||
}
|
||||
|
||||
private AutocryptInteractor(AutocryptPeerDataAccessObject autocryptPeerDao,
|
||||
KeyWritableRepository keyWritableRepository) {
|
||||
private AutocryptInteractor(AutocryptPeerDao autocryptPeerDao,
|
||||
KeyWritableRepository keyWritableRepository, String packageName) {
|
||||
this.autocryptPeerDao = autocryptPeerDao;
|
||||
this.keyWritableRepository = keyWritableRepository;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) {
|
||||
AutocryptPeer currentAutocryptPeer = autocryptPeerDao.getAutocryptPeer(packageName, autocryptPeerId);
|
||||
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 && effectiveDate.compareTo(lastSeenAutocrypt) <= 0) {
|
||||
Date lastSeenKey = currentAutocryptPeer != null ? currentAutocryptPeer.last_seen_key() : null;
|
||||
if (lastSeenKey != null && effectiveDate.compareTo(lastSeenKey) <= 0) {
|
||||
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);
|
||||
Date lastSeen = currentAutocryptPeer != null ? currentAutocryptPeer.last_seen() : null;
|
||||
if (lastSeen == null || effectiveDate.after(lastSeen)) {
|
||||
autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate);
|
||||
autocryptPeerDao.insertOrUpdateLastSeen(packageName, autocryptPeerId, effectiveDate);
|
||||
}
|
||||
|
||||
// 3. If the Autocrypt header is unavailable, no further changes are required and the update process terminates.
|
||||
@@ -66,17 +78,18 @@ class AutocryptInteractor {
|
||||
// 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);
|
||||
autocryptPeerDao.updateKey(packageName, autocryptPeerId, effectiveDate, newMasterKeyId, isMutual);
|
||||
}
|
||||
|
||||
void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) {
|
||||
AutocryptPeer currentAutocryptPeer = autocryptPeerDao.getAutocryptPeer(packageName, autocryptPeerId);
|
||||
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);
|
||||
Date lastSeenGossip = currentAutocryptPeer.gossip_last_seen_key();
|
||||
if (lastSeenGossip != null && lastSeenGossip.after(effectiveDate)) {
|
||||
return;
|
||||
}
|
||||
@@ -94,7 +107,8 @@ class AutocryptInteractor {
|
||||
// 4. Set peers[gossip-addr].gossip_key to the value of the keydata attribute.
|
||||
Long newMasterKeyId = saveKeyringResult.savedMasterKeyId;
|
||||
|
||||
autocryptPeerDao.updateKeyGossipFromAutocrypt(autocryptPeerId, effectiveDate, newMasterKeyId);
|
||||
autocryptPeerDao.updateKeyGossip(packageName, autocryptPeerId, effectiveDate, newMasterKeyId,
|
||||
GossipOrigin.GOSSIP_HEADER);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -131,4 +145,97 @@ class AutocryptInteractor {
|
||||
}
|
||||
return uncachedKeyRing;
|
||||
}
|
||||
|
||||
public List<AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
|
||||
List<AutocryptRecommendationResult> result = new ArrayList<>(autocryptIds.length);
|
||||
|
||||
for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) {
|
||||
AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus);
|
||||
result.add(peerResult);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Determines Autocrypt "ui-recommendation", according to spec.
|
||||
* See https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages
|
||||
*/
|
||||
private AutocryptRecommendationResult determineAutocryptRecommendation(AutocryptKeyStatus autocryptKeyStatus) {
|
||||
AutocryptRecommendationResult keyRecommendation = determineAutocryptKeyRecommendation(autocryptKeyStatus);
|
||||
if (keyRecommendation != null) return keyRecommendation;
|
||||
|
||||
AutocryptRecommendationResult gossipRecommendation = determineAutocryptGossipRecommendation(autocryptKeyStatus);
|
||||
if (gossipRecommendation != null) return gossipRecommendation;
|
||||
|
||||
return new AutocryptRecommendationResult(autocryptKeyStatus.autocryptPeer().identifier(), AutocryptState.DISABLE, null, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AutocryptRecommendationResult determineAutocryptKeyRecommendation(AutocryptKeyStatus autocryptKeyStatus) {
|
||||
boolean hasKey = autocryptKeyStatus.hasKey();
|
||||
boolean isRevoked = autocryptKeyStatus.isKeyRevoked();
|
||||
boolean isExpired = autocryptKeyStatus.isKeyExpired();
|
||||
if (!hasKey || isRevoked || isExpired) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AutocryptPeer autocryptPeer = autocryptKeyStatus.autocryptPeer();
|
||||
long masterKeyId = autocryptPeer.master_key_id();
|
||||
Date lastSeen = autocryptPeer.last_seen();
|
||||
Date lastSeenKey = autocryptPeer.last_seen_key();
|
||||
boolean isVerified = autocryptKeyStatus.isKeyVerified();
|
||||
if (lastSeenKey.getTime() < (lastSeen.getTime() - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) {
|
||||
return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified);
|
||||
}
|
||||
|
||||
boolean isMutual = autocryptPeer.is_mutual();
|
||||
if (isMutual) {
|
||||
return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.MUTUAL, masterKeyId, isVerified);
|
||||
} else {
|
||||
return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.AVAILABLE, masterKeyId, isVerified);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AutocryptRecommendationResult determineAutocryptGossipRecommendation(AutocryptKeyStatus autocryptKeyStatus) {
|
||||
boolean gossipHasKey = autocryptKeyStatus.hasGossipKey();
|
||||
boolean gossipIsRevoked = autocryptKeyStatus.isGossipKeyRevoked();
|
||||
boolean gossipIsExpired = autocryptKeyStatus.isGossipKeyExpired();
|
||||
boolean isVerified = autocryptKeyStatus.isGossipKeyVerified();
|
||||
|
||||
if (!gossipHasKey || gossipIsRevoked || gossipIsExpired) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Long masterKeyId = autocryptKeyStatus.autocryptPeer().gossip_master_key_id();
|
||||
return new AutocryptRecommendationResult(autocryptKeyStatus.autocryptPeer().identifier(), AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified);
|
||||
}
|
||||
|
||||
public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) {
|
||||
autocryptPeerDao.updateKeyGossip(packageName, autocryptId, effectiveDate, masterKeyId, GossipOrigin.SIGNATURE);
|
||||
}
|
||||
|
||||
public void updateKeyGossipFromDedup(String autocryptId, long masterKeyId) {
|
||||
autocryptPeerDao.updateKeyGossip(packageName, autocryptId, new Date(), masterKeyId, GossipOrigin.DEDUP);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,6 @@ 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.DatabaseNotifyManager;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
@@ -52,8 +48,9 @@ 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.remote.AutocryptInteractor.AutocryptRecommendationResult;
|
||||
import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -69,8 +66,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
|
||||
private UriMatcher uriMatcher;
|
||||
private ApiPermissionHelper apiPermissionHelper;
|
||||
private KeychainProvider internalKeychainProvider;
|
||||
private DatabaseNotifyManager databaseNotifyManager;
|
||||
|
||||
|
||||
/**
|
||||
@@ -107,10 +102,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
throw new NullPointerException("Context can't be null during onCreate!");
|
||||
}
|
||||
|
||||
internalKeychainProvider = new KeychainProvider();
|
||||
internalKeychainProvider.attachInfo(context, null);
|
||||
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext()));
|
||||
databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -267,10 +259,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!");
|
||||
}
|
||||
if (!isWildcardSelector && queriesAutocryptResult) {
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao =
|
||||
new AutocryptPeerDataAccessObject(internalKeychainProvider, callingPackageName,
|
||||
databaseNotifyManager);
|
||||
fillTempTableWithAutocryptRecommendations(db, autocryptPeerDao, selectionArgs);
|
||||
AutocryptInteractor autocryptInteractor =
|
||||
AutocryptInteractor.getInstance(getContext(), callingPackageName);
|
||||
fillTempTableWithAutocryptRecommendations(db, autocryptInteractor, selectionArgs);
|
||||
}
|
||||
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
@@ -310,7 +301,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
}
|
||||
|
||||
qb.setStrict(true);
|
||||
String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy);
|
||||
String query = qb.buildQuery(projection, null, groupBy, null, orderBy, null);
|
||||
Cursor cursor = db.query(query);
|
||||
if (cursor != null) {
|
||||
// Tell the cursor what uri to watch, so it knows when its source data changes
|
||||
@@ -327,9 +318,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
}
|
||||
|
||||
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
|
||||
AutocryptInteractor autocryptInteractor, String[] peerIds) {
|
||||
List<AutocryptRecommendationResult> autocryptStates =
|
||||
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
|
||||
autocryptInteractor.determineAutocryptRecommendations(peerIds);
|
||||
|
||||
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
|
||||
}
|
||||
|
||||
@@ -68,10 +68,9 @@ import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
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;
|
||||
@@ -585,8 +584,8 @@ public class OpenPgpService extends Service {
|
||||
return signatureResult;
|
||||
}
|
||||
|
||||
AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(),
|
||||
mApiPermissionHelper.getCurrentCallingPackage());
|
||||
AutocryptPeerDao autocryptPeerentityDao =
|
||||
AutocryptPeerDao.getInstance(getBaseContext());
|
||||
Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeerId);
|
||||
|
||||
long masterKeyId = signatureResult.getKeyId();
|
||||
@@ -596,7 +595,9 @@ public class OpenPgpService extends Service {
|
||||
if (effectiveTime.after(now)) {
|
||||
effectiveTime = now;
|
||||
}
|
||||
autocryptPeerentityDao.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId);
|
||||
AutocryptInteractor autocryptInteractor =
|
||||
AutocryptInteractor.getInstance(this, mApiPermissionHelper.getCurrentCallingPackage());
|
||||
autocryptInteractor.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId);
|
||||
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW);
|
||||
} else if (masterKeyId == autocryptPeerMasterKeyId) {
|
||||
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK);
|
||||
@@ -860,9 +861,8 @@ public class OpenPgpService extends Service {
|
||||
|
||||
private Intent updateAutocryptPeerImpl(Intent data) {
|
||||
try {
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao = new AutocryptPeerDataAccessObject(getBaseContext(),
|
||||
mApiPermissionHelper.getCurrentCallingPackage());
|
||||
AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getBaseContext(), autocryptPeerDao);
|
||||
AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(
|
||||
getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage());
|
||||
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) &&
|
||||
data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.sufficientlysecure.keychain.remote.ui.dialog;
|
||||
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -31,10 +30,10 @@ import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.remote.AutocryptInteractor;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -44,7 +43,7 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks<List<KeyInfo>> {
|
||||
private final int loaderId;
|
||||
|
||||
|
||||
private AutocryptPeerDataAccessObject autocryptPeerDao;
|
||||
private AutocryptInteractor autocryptInteractor;
|
||||
private String duplicateAddress;
|
||||
|
||||
private RemoteDeduplicateView view;
|
||||
@@ -73,7 +72,7 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks<List<KeyInfo>> {
|
||||
return;
|
||||
}
|
||||
|
||||
autocryptPeerDao = new AutocryptPeerDataAccessObject(context, packageName);
|
||||
this.autocryptInteractor = AutocryptInteractor.getInstance(context, packageName);
|
||||
|
||||
this.duplicateAddress = duplicateAddress;
|
||||
view.setAddressText(duplicateAddress);
|
||||
@@ -121,8 +120,8 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks<List<KeyInfo>> {
|
||||
return;
|
||||
}
|
||||
|
||||
long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId();
|
||||
autocryptPeerDao.updateKeyGossipFromDedup(duplicateAddress, new Date(), masterKeyId);
|
||||
long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId();
|
||||
autocryptInteractor.updateKeyGossipFromDedup(duplicateAddress, masterKeyId);
|
||||
|
||||
view.finish();
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ import com.google.auto.value.AutoValue;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.ui.util.PackageIconGetter;
|
||||
@@ -71,30 +72,26 @@ public class IdentityDao {
|
||||
|
||||
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 PackageManager packageManager;
|
||||
private final AutocryptPeerDao autocryptPeerDao;
|
||||
|
||||
static IdentityDao getInstance(Context context) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageIconGetter iconGetter = PackageIconGetter.getInstance(context);
|
||||
return new IdentityDao(contentResolver, packageManager, iconGetter);
|
||||
AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context);
|
||||
return new IdentityDao(contentResolver, packageManager, iconGetter, autocryptPeerDao);
|
||||
}
|
||||
|
||||
private IdentityDao(ContentResolver contentResolver, PackageManager packageManager, PackageIconGetter iconGetter) {
|
||||
private IdentityDao(ContentResolver contentResolver, PackageManager packageManager, PackageIconGetter iconGetter,
|
||||
AutocryptPeerDao autocryptPeerDao) {
|
||||
this.packageManager = packageManager;
|
||||
this.contentResolver = contentResolver;
|
||||
this.packageIconGetter = iconGetter;
|
||||
this.autocryptPeerDao = autocryptPeerDao;
|
||||
}
|
||||
|
||||
List<IdentityInfo> getIdentityInfos(long masterKeyId, boolean showLinkedIds) {
|
||||
@@ -110,35 +107,24 @@ public class IdentityDao {
|
||||
}
|
||||
|
||||
private void correlateOrAddAutocryptPeers(ArrayList<IdentityInfo> identities, long masterKeyId) {
|
||||
Cursor cursor = contentResolver.query(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId),
|
||||
AUTOCRYPT_PEER_PROJECTION, null, null, null);
|
||||
if (cursor == null) {
|
||||
Timber.e("Error loading Autocrypt peers");
|
||||
return;
|
||||
}
|
||||
for (AutocryptPeer autocryptPeer : autocryptPeerDao.getAutocryptPeersForKey(masterKeyId)) {
|
||||
String packageName = autocryptPeer.package_name();
|
||||
String autocryptId = autocryptPeer.identifier();
|
||||
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
String packageName = cursor.getString(INDEX_PACKAGE_NAME);
|
||||
String autocryptPeer = cursor.getString(INDEX_IDENTIFIER);
|
||||
Drawable drawable = packageIconGetter.getDrawableForPackageName(packageName);
|
||||
Intent autocryptPeerIntent = getAutocryptPeerActivityIntentIfResolvable(packageName, autocryptId);
|
||||
|
||||
Drawable drawable = packageIconGetter.getDrawableForPackageName(packageName);
|
||||
Intent autocryptPeerIntent = getAutocryptPeerActivityIntentIfResolvable(packageName, autocryptPeer);
|
||||
|
||||
UserIdInfo associatedUserIdInfo = findUserIdMatchingAutocryptPeer(identities, autocryptPeer);
|
||||
if (associatedUserIdInfo != null) {
|
||||
int position = identities.indexOf(associatedUserIdInfo);
|
||||
AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo
|
||||
.create(associatedUserIdInfo, autocryptPeer, packageName, drawable, autocryptPeerIntent);
|
||||
identities.set(position, autocryptPeerInfo);
|
||||
} else {
|
||||
AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo
|
||||
.create(autocryptPeer, packageName, drawable, autocryptPeerIntent);
|
||||
identities.add(autocryptPeerInfo);
|
||||
}
|
||||
UserIdInfo associatedUserIdInfo = findUserIdMatchingAutocryptPeer(identities, autocryptId);
|
||||
if (associatedUserIdInfo != null) {
|
||||
int position = identities.indexOf(associatedUserIdInfo);
|
||||
AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo
|
||||
.create(associatedUserIdInfo, autocryptId, packageName, drawable, autocryptPeerIntent);
|
||||
identities.set(position, autocryptPeerInfo);
|
||||
} else {
|
||||
AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo
|
||||
.create(autocryptId, packageName, drawable, autocryptPeerIntent);
|
||||
identities.add(autocryptPeerInfo);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter;
|
||||
@@ -56,12 +56,14 @@ public class IdentitiesPresenter implements Observer<List<IdentityInfo>> {
|
||||
private final long masterKeyId;
|
||||
private final boolean isSecret;
|
||||
private final boolean showLinkedIds;
|
||||
private AutocryptPeerDao autocryptPeerDao;
|
||||
|
||||
public IdentitiesPresenter(Context context, IdentitiesMvpView view, ViewKeyMvpView viewKeyMvpView,
|
||||
long masterKeyId, boolean isSecret) {
|
||||
this.context = context;
|
||||
this.view = view;
|
||||
this.viewKeyMvpView = viewKeyMvpView;
|
||||
this.autocryptPeerDao = AutocryptPeerDao.getInstance(context);
|
||||
|
||||
this.masterKeyId = masterKeyId;
|
||||
this.isSecret = isSecret;
|
||||
@@ -146,9 +148,7 @@ public class IdentitiesPresenter implements Observer<List<IdentityInfo>> {
|
||||
return;
|
||||
}
|
||||
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao =
|
||||
new AutocryptPeerDataAccessObject(context, info.getPackageName());
|
||||
autocryptPeerDao.delete(info.getIdentity());
|
||||
autocryptPeerDao.deleteByIdentifier(info.getPackageName(), info.getIdentity());
|
||||
}
|
||||
|
||||
public LiveData<List<IdentityInfo>> getLiveDataInstance() {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import java.util.Date;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autocrypt_peers (
|
||||
package_name TEXT NOT NULL,
|
||||
identifier TEXT NOT NULL,
|
||||
last_seen INTEGER AS Date NULL,
|
||||
last_seen_key INTEGER AS Date NULL,
|
||||
is_mutual INTEGER AS Boolean NOT NULL DEFAULT 0,
|
||||
master_key_id INTEGER NULL,
|
||||
gossip_master_key_id INTEGER NULL,
|
||||
gossip_last_seen_key INTEGER AS Date NULL,
|
||||
gossip_origin INTEGER AS GossipOrigin NULL,
|
||||
PRIMARY KEY(package_name, identifier),
|
||||
FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
selectByIdentifiers:
|
||||
SELECT *
|
||||
FROM autocrypt_peers
|
||||
WHERE package_name = ? AND identifier IN ?;
|
||||
|
||||
selectByMasterKeyId:
|
||||
SELECT *
|
||||
FROM autocrypt_peers
|
||||
WHERE master_key_id = ?;
|
||||
|
||||
selectMasterKeyIdByIdentifier:
|
||||
SELECT master_key_id
|
||||
FROM autocrypt_peers
|
||||
WHERE identifier = ?;
|
||||
|
||||
deleteByIdentifier:
|
||||
DELETE FROM autocrypt_peers
|
||||
WHERE package_name = ? AND identifier = ?;
|
||||
|
||||
deleteByMasterKeyId:
|
||||
DELETE FROM autocrypt_peers
|
||||
WHERE master_key_id = ?;
|
||||
|
||||
updateLastSeen:
|
||||
UPDATE autocrypt_peers SET last_seen = ?3 WHERE package_name = ?1 AND identifier = ?2;
|
||||
|
||||
updateKey:
|
||||
UPDATE autocrypt_peers SET last_seen_key = ?3, master_key_id = ?4, is_mutual = ?5 WHERE package_name = ?1 AND identifier = ?2;
|
||||
|
||||
updateGossipKey:
|
||||
UPDATE autocrypt_peers SET gossip_last_seen_key = ?3, gossip_master_key_id = ?4, gossip_origin = ?5 WHERE package_name = ?1 AND identifier = ?2;
|
||||
|
||||
insertPeer:
|
||||
INSERT INTO autocrypt_peers (package_name, identifier, last_seen) VALUES (?, ?, ?);
|
||||
|
||||
selectAutocryptKeyStatus:
|
||||
SELECT autocryptPeer.*,
|
||||
(CASE WHEN ac_key.expiry IS NULL THEN 0 WHEN ac_key.expiry > ?3 THEN 0 ELSE 1 END) AS key_is_expired_int,
|
||||
(CASE WHEN gossip_key.expiry IS NULL THEN 0 WHEN gossip_key.expiry > ?3 THEN 0 ELSE 1 END) AS gossip_key_is_expired_int,
|
||||
ac_key.is_revoked AS key_is_revoked_int,
|
||||
gossip_key.is_revoked AS gossip_key_is_revoked_int,
|
||||
EXISTS (SELECT * FROM certs WHERE certs.master_key_id = autocryptPeer.master_key_id AND verified = 1 ) AS key_is_verified_int,
|
||||
EXISTS (SELECT * FROM certs WHERE certs.master_key_id = autocryptPeer.gossip_master_key_id AND verified = 1 ) AS gossip_key_is_verified_int
|
||||
FROM autocrypt_peers AS autocryptPeer
|
||||
LEFT JOIN keys AS ac_key ON (ac_key.master_key_id = autocryptPeer.master_key_id AND ac_key.rank = 0)
|
||||
LEFT JOIN keys AS gossip_key ON (gossip_key.master_key_id = gossip_master_key_id AND gossip_key.rank = 0)
|
||||
WHERE package_name = ?1 AND identifier IN ?2;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- TODO implement. this is only here for reference in SQLDelight
|
||||
CREATE TABLE IF NOT EXISTS certs(
|
||||
master_key_id INTEGER,
|
||||
rank INTEGER,
|
||||
key_id_certifier INTEGER,
|
||||
type INTEGER,
|
||||
verified INTEGER,
|
||||
creation INTEGER,
|
||||
data BLOB,
|
||||
PRIMARY KEY(master_key_id, rank, key_id_certifier),
|
||||
FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE
|
||||
-- FOREIGN KEY(master_key_id, rank) REFERENCES user_packets(master_key_id, rank) ON DELETE CASCADE
|
||||
);
|
||||
@@ -246,7 +246,8 @@ public class InteropTest {
|
||||
KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application,
|
||||
LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application),
|
||||
LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application),
|
||||
DatabaseNotifyManager.create(RuntimeEnvironment.application)) {
|
||||
DatabaseNotifyManager.create(RuntimeEnvironment.application),
|
||||
AutocryptPeerDao.getInstance(RuntimeEnvironment.application)) {
|
||||
|
||||
@Override
|
||||
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {
|
||||
|
||||
@@ -19,12 +19,13 @@ import org.robolectric.shadows.ShadowLog;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
||||
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest;
|
||||
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
|
||||
@@ -63,7 +64,7 @@ public class KeychainExternalProviderTest {
|
||||
ContentResolver contentResolver = RuntimeEnvironment.application.getContentResolver();
|
||||
ApiPermissionHelper apiPermissionHelper;
|
||||
ApiDataAccessObject apiDao;
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao;
|
||||
AutocryptPeerDao autocryptPeerDao;
|
||||
|
||||
|
||||
@Before
|
||||
@@ -81,9 +82,9 @@ public class KeychainExternalProviderTest {
|
||||
|
||||
apiDao = new ApiDataAccessObject(RuntimeEnvironment.application);
|
||||
apiPermissionHelper = new ApiPermissionHelper(RuntimeEnvironment.application, apiDao);
|
||||
autocryptPeerDao = new AutocryptPeerDataAccessObject(RuntimeEnvironment.application, PACKAGE_NAME);
|
||||
autocryptPeerDao = AutocryptPeerDao.getInstance(RuntimeEnvironment.application);
|
||||
|
||||
apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, PACKAGE_SIGNATURE));
|
||||
apiDao.insertApiApp(ApiApp.create(PACKAGE_NAME, PACKAGE_SIGNATURE));
|
||||
}
|
||||
|
||||
@Test(expected = AccessControlException.class)
|
||||
@@ -100,7 +101,7 @@ public class KeychainExternalProviderTest {
|
||||
@Test(expected = AccessControlException.class)
|
||||
public void testPermission__withWrongPackageCert() throws Exception {
|
||||
apiDao.deleteApiApp(PACKAGE_NAME);
|
||||
apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
|
||||
apiDao.insertApiApp(ApiApp.create(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
|
||||
|
||||
contentResolver.query(
|
||||
EmailStatus.CONTENT_URI,
|
||||
@@ -208,7 +209,8 @@ public class KeychainExternalProviderTest {
|
||||
insertSecretKeyringFrom("/test-keys/testring.sec");
|
||||
insertPublicKeyringFrom("/test-keys/testring.pub");
|
||||
|
||||
autocryptPeerDao.updateKey(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false);
|
||||
autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date());
|
||||
autocryptPeerDao.updateKey(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false);
|
||||
|
||||
Cursor cursor = contentResolver.query(
|
||||
AutocryptStatus.CONTENT_URI, new String[] {
|
||||
@@ -235,7 +237,8 @@ public class KeychainExternalProviderTest {
|
||||
insertSecretKeyringFrom("/test-keys/testring.sec");
|
||||
insertPublicKeyringFrom("/test-keys/testring.pub");
|
||||
|
||||
autocryptPeerDao.updateKey(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, true);
|
||||
autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date());
|
||||
autocryptPeerDao.updateKey(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, true);
|
||||
|
||||
Cursor cursor = contentResolver.query(
|
||||
AutocryptStatus.CONTENT_URI, new String[] {
|
||||
@@ -262,7 +265,8 @@ public class KeychainExternalProviderTest {
|
||||
insertSecretKeyringFrom("/test-keys/testring.sec");
|
||||
insertPublicKeyringFrom("/test-keys/testring.pub");
|
||||
|
||||
autocryptPeerDao.updateKey("tid", new Date(), KEY_ID_PUBLIC, false);
|
||||
autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date());
|
||||
autocryptPeerDao.updateKey(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false);
|
||||
certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1);
|
||||
|
||||
Cursor cursor = contentResolver.query(
|
||||
@@ -306,8 +310,9 @@ public class KeychainExternalProviderTest {
|
||||
insertSecretKeyringFrom("/test-keys/testring.sec");
|
||||
insertPublicKeyringFrom("/test-keys/testring.pub");
|
||||
|
||||
autocryptPeerDao.updateKeyGossipFromAutocrypt("tid", new Date(), KEY_ID_PUBLIC);
|
||||
autocryptPeerDao.delete("tid");
|
||||
autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date());
|
||||
autocryptPeerDao.updateKeyGossip(PACKAGE_NAME, "tid", new Date(), KEY_ID_PUBLIC, GossipOrigin.GOSSIP_HEADER);
|
||||
autocryptPeerDao.deleteByIdentifier(PACKAGE_NAME, "tid");
|
||||
|
||||
Cursor cursor = contentResolver.query(
|
||||
AutocryptStatus.CONTENT_URI, new String[] {
|
||||
@@ -331,7 +336,8 @@ public class KeychainExternalProviderTest {
|
||||
insertSecretKeyringFrom("/test-keys/testring.sec");
|
||||
insertPublicKeyringFrom("/test-keys/testring.pub");
|
||||
|
||||
autocryptPeerDao.updateKeyGossipFromAutocrypt(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC);
|
||||
autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date());
|
||||
autocryptPeerDao.updateKeyGossip(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, GossipOrigin.GOSSIP_HEADER);
|
||||
certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1);
|
||||
|
||||
Cursor cursor = contentResolver.query(
|
||||
|
||||
Reference in New Issue
Block a user