Merge pull request #2375 from open-keychain/ditch-keychain-provider

Ditch keychain provider
This commit is contained in:
Vincent Breitmoser
2018-07-13 16:56:24 +02:00
committed by GitHub
29 changed files with 626 additions and 831 deletions

View File

@@ -880,13 +880,10 @@
android:name=".service.KeychainService" android:name=".service.KeychainService"
android:exported="false" /> android:exported="false" />
<!-- label is made to be "Keyserver Sync" since that is the only context in which
the user will see it-->
<provider <provider
android:name=".provider.KeychainProvider" android:name=".provider.KeychainProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false" />
android:label="@string/keyserver_sync_settings_title" />
<provider <provider
android:name=".remote.KeychainExternalProvider" android:name=".remote.KeychainExternalProvider"

View File

@@ -51,7 +51,7 @@ import timber.log.Timber;
*/ */
public class KeychainDatabase { public class KeychainDatabase {
private static final String DATABASE_NAME = "openkeychain.db"; private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 29; private static final int DATABASE_VERSION = 30;
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
private static KeychainDatabase sInstance; private static KeychainDatabase sInstance;
@@ -133,6 +133,8 @@ public class KeychainDatabase {
db.execSQL(AutocryptPeersModel.CREATE_TABLE); db.execSQL(AutocryptPeersModel.CREATE_TABLE);
db.execSQL(ApiAllowedKeysModel.CREATE_TABLE); db.execSQL(ApiAllowedKeysModel.CREATE_TABLE);
db.execSQL(KeysModel.UNIFIEDKEYVIEW); db.execSQL(KeysModel.UNIFIEDKEYVIEW);
db.execSQL(KeysModel.VALIDKEYSVIEW);
db.execSQL(UserPacketsModel.UIDSTATUS);
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");");
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
@@ -356,18 +358,28 @@ public class KeychainDatabase {
renameApiAutocryptPeersTable(db); renameApiAutocryptPeersTable(db);
case 28: case 28:
recreateUnifiedKeyView(db);
// drop old table from version 20 // drop old table from version 20
db.execSQL("DROP TABLE IF EXISTS api_accounts"); db.execSQL("DROP TABLE IF EXISTS api_accounts");
case 29:
recreateUnifiedKeyView(db);
} }
} }
private void recreateUnifiedKeyView(SupportSQLiteDatabase db) { private void recreateUnifiedKeyView(SupportSQLiteDatabase db) {
try { try {
db.beginTransaction(); db.beginTransaction();
// noinspection deprecation // noinspection deprecation
db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME); db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME);
db.execSQL(KeysModel.UNIFIEDKEYVIEW); db.execSQL(KeysModel.UNIFIEDKEYVIEW);
// noinspection deprecation
db.execSQL("DROP VIEW IF EXISTS " + KeysModel.VALIDMASTERKEYS_VIEW_NAME);
db.execSQL(KeysModel.VALIDKEYSVIEW);
// noinspection deprecation
db.execSQL("DROP VIEW IF EXISTS " + UserPacketsModel.UIDSTATUS_VIEW_NAME);
db.execSQL(UserPacketsModel.UIDSTATUS);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@@ -8,6 +8,7 @@ import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.SupportSQLiteQuery; import android.arch.persistence.db.SupportSQLiteQuery;
import android.database.Cursor; import android.database.Cursor;
import com.squareup.sqldelight.RowMapper;
import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
@@ -33,7 +34,7 @@ class AbstractDao {
return databaseNotifyManager; return databaseNotifyManager;
} }
<T> List<T> mapAllRows(SupportSQLiteQuery query, Mapper<T> mapper) { <T> List<T> mapAllRows(SupportSQLiteQuery query, RowMapper<T> mapper) {
ArrayList<T> result = new ArrayList<>(); ArrayList<T> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) { try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
@@ -44,7 +45,7 @@ class AbstractDao {
return result; return result;
} }
<T> T mapSingleRowOrThrow(SupportSQLiteQuery query, Mapper<T> mapper) throws NotFoundException { <T> T mapSingleRowOrThrow(SupportSQLiteQuery query, RowMapper<T> mapper) throws NotFoundException {
T result = mapSingleRow(query, mapper); T result = mapSingleRow(query, mapper);
if (result == null) { if (result == null) {
throw new NotFoundException(); throw new NotFoundException();
@@ -52,7 +53,7 @@ class AbstractDao {
return result; return result;
} }
<T> T mapSingleRow(SupportSQLiteQuery query, Mapper<T> mapper) { <T> T mapSingleRow(SupportSQLiteQuery query, RowMapper<T> mapper) {
try (Cursor cursor = getReadableDb().query(query)) { try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
return mapper.map(cursor); return mapper.map(cursor);
@@ -60,8 +61,4 @@ class AbstractDao {
} }
return null; return null;
} }
interface Mapper<T> {
T map(Cursor cursor);
}
} }

View File

@@ -0,0 +1,100 @@
package org.sufficientlysecure.keychain.daos;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import org.sufficientlysecure.keychain.CertsModel.InsertCert;
import org.sufficientlysecure.keychain.KeyRingsPublicModel.InsertKeyRingPublic;
import org.sufficientlysecure.keychain.KeySignaturesModel.InsertKeySignature;
import org.sufficientlysecure.keychain.KeysModel.InsertKey;
import org.sufficientlysecure.keychain.UserPacketsModel.InsertUserPacket;
import org.sufficientlysecure.keychain.model.Certification;
import org.sufficientlysecure.keychain.model.KeyRingPublic;
import org.sufficientlysecure.keychain.model.KeySignature;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.UserPacket;
public class DatabaseBatchInteractor {
private final SupportSQLiteDatabase db;
private final InsertKeyRingPublic insertKeyRingPublicStatement;
private final InsertKey insertSubKeyStatement;
private final InsertUserPacket insertUserPacketStatement;
private final InsertCert insertCertificationStatement;
private final InsertKeySignature insertKeySignerStatement;
DatabaseBatchInteractor(SupportSQLiteDatabase db) {
this.db = db;
insertKeyRingPublicStatement = KeyRingPublic.createInsertStatement(db);
insertSubKeyStatement = SubKey.createInsertStatement(db);
insertUserPacketStatement = UserPacket.createInsertStatement(db);
insertCertificationStatement = Certification.createInsertStatement(db);
insertKeySignerStatement = KeySignature.createInsertStatement(db);
}
public SupportSQLiteDatabase getDb() {
return db;
}
public void applyBatch(List<BatchOp> operations) {
for (BatchOp op : operations) {
if (op.keyRingPublic != null) {
op.keyRingPublic.bindTo(insertKeyRingPublicStatement);
insertKeyRingPublicStatement.executeInsert();
} else if (op.subKey != null) {
op.subKey.bindTo(insertSubKeyStatement);
insertSubKeyStatement.executeInsert();
} else if (op.userPacket != null) {
op.userPacket.bindTo(insertUserPacketStatement);
insertUserPacketStatement.executeInsert();
} else if (op.certification != null) {
op.certification.bindTo(insertCertificationStatement);
insertCertificationStatement.executeInsert();
} else if (op.keySignature != null) {
op.keySignature.bindTo(insertKeySignerStatement);
insertKeySignerStatement.executeInsert();
}
}
}
public static BatchOp createInsertKeyRingPublic(KeyRingPublic keyRingPublic) {
return new BatchOp(keyRingPublic, null, null, null, null);
}
static BatchOp createInsertSubKey(SubKey subKey) {
return new BatchOp(null, subKey, null, null, null);
}
public static BatchOp createInsertUserPacket(UserPacket userPacket) {
return new BatchOp(null, null, userPacket, null, null);
}
public static BatchOp createInsertCertification(Certification certification) {
return new BatchOp(null, null, null, certification, null);
}
static BatchOp createInsertSignerKey(KeySignature keySignature) {
return new BatchOp(null, null, null, null, keySignature);
}
static class BatchOp {
final KeyRingPublic keyRingPublic;
final SubKey subKey;
final UserPacket userPacket;
final Certification certification;
final KeySignature keySignature;
BatchOp(KeyRingPublic keyRingPublic, SubKey subKey, UserPacket userPacket,
Certification certification, KeySignature keySignature) {
this.subKey = subKey;
this.keyRingPublic = keyRingPublic;
this.userPacket = userPacket;
this.certification = certification;
this.keySignature = keySignature;
}
}
}

View File

@@ -23,6 +23,11 @@ public class DatabaseNotifyManager {
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
} }
public void notifyAllKeysChange() {
Uri uri = getNotifyUriAllKeys();
contentResolver.notifyChange(uri, null);
}
public void notifyKeyChange(long masterKeyId) { public void notifyKeyChange(long masterKeyId) {
Uri uri = getNotifyUriMasterKeyId(masterKeyId); Uri uri = getNotifyUriMasterKeyId(masterKeyId);
contentResolver.notifyChange(uri, null); contentResolver.notifyChange(uri, null);

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.daos;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.content.ContentResolver; import android.content.ContentResolver;
@@ -27,6 +28,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import com.squareup.sqldelight.RowMapper;
import com.squareup.sqldelight.SqlDelightQuery; import com.squareup.sqldelight.SqlDelightQuery;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.KeychainDatabase;
@@ -129,69 +131,133 @@ public class KeyRepository extends AbstractDao {
public List<Long> getAllMasterKeyIds() { public List<Long> getAllMasterKeyIds() {
SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds(); SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds();
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); ArrayList<Long> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
Long item = KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper().map(cursor);
result.add(item);
}
}
return result;
} }
public List<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) { public List<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) {
long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds); long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds);
SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds);
return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper()::map); ArrayList<Long> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
Long item = KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper().map(cursor);
result.add(item);
}
}
return result;
} }
public Long getMasterKeyIdBySubkeyId(long subKeyId) { public Long getMasterKeyIdBySubkeyId(long subKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId); SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId);
return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()::map); return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper());
} }
public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) { public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId); SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId);
return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER);
} }
public List<UnifiedKeyInfo> getUnifiedKeyInfo(long... masterKeyIds) { public List<UnifiedKeyInfo> getUnifiedKeyInfo(long... masterKeyIds) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds); SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds);
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); ArrayList<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(item);
}
}
return result;
} }
public List<UnifiedKeyInfo> getUnifiedKeyInfosByMailAddress(String mailAddress) { public List<UnifiedKeyInfo> getUnifiedKeyInfosByMailAddress(String mailAddress) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%'); SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%');
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); ArrayList<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(item);
}
}
return result;
} }
public List<UnifiedKeyInfo> getAllUnifiedKeyInfo() { public List<UnifiedKeyInfo> getAllUnifiedKeyInfo() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); ArrayList<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(item);
}
}
return result;
} }
public List<UnifiedKeyInfo> getAllUnifiedKeyInfoWithSecret() { public List<UnifiedKeyInfo> getAllUnifiedKeyInfoWithSecret() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret(); SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret();
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); ArrayList<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(item);
}
}
return result;
} }
public List<UserId> getUserIds(long... masterKeyIds) { public List<UserId> getUserIds(long... masterKeyIds) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds); SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds);
return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); ArrayList<UserId> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UserId item = UserPacket.USER_ID_MAPPER.map(cursor);
result.add(item);
}
}
return result;
} }
public List<String> getConfirmedUserIds(long masterKeyId) { public List<String> getConfirmedUserIds(long masterKeyId) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification( SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(
Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET); Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET);
return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id()); ArrayList<String> result = new ArrayList<>();
try (Cursor cursor1 = getReadableDb().query(query)) {
while (cursor1.moveToNext()) {
String item = ((RowMapper<String>) (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id())
.map(cursor1);
result.add(item);
}
}
return result;
} }
public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) { public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId); SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId);
return mapAllRows(query, SubKey.SUBKEY_MAPPER::map); ArrayList<SubKey> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
SubKey item = SubKey.SUBKEY_MAPPER.map(cursor);
result.add(item);
}
}
return result;
} }
public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId); SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId);
return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER::map); return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER);
} }
public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException { public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId); SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId);
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()::map); return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper());
} }
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException { private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException {
@@ -247,12 +313,12 @@ public class KeyRepository extends AbstractDao {
public long getSecretSignId(long masterKeyId) throws NotFoundException { public long getSecretSignId(long masterKeyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId); SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId);
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map); return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper());
} }
public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException { public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId); SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId);
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map); return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper());
} }
public static class NotFoundException extends Exception { public static class NotFoundException extends Exception {

View File

@@ -25,21 +25,24 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import android.content.ContentProviderOperation; import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.util.LongSparseArray; import android.support.v4.util.LongSparseArray;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId; import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId;
import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.KeysModel.UpdateHasSecretByKeyId;
import org.sufficientlysecure.keychain.KeysModel.UpdateHasSecretByMasterKeyId;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.CustomColumnAdapters; import org.sufficientlysecure.keychain.daos.DatabaseBatchInteractor.BatchOp;
import org.sufficientlysecure.keychain.model.Certification;
import org.sufficientlysecure.keychain.model.KeyRingPublic;
import org.sufficientlysecure.keychain.model.KeySignature;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.model.UserPacket;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
@@ -58,12 +61,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
@@ -87,6 +84,7 @@ public class KeyWritableRepository extends KeyRepository {
private final Context context; private final Context context;
private final DatabaseNotifyManager databaseNotifyManager; private final DatabaseNotifyManager databaseNotifyManager;
private AutocryptPeerDao autocryptPeerDao; private AutocryptPeerDao autocryptPeerDao;
private DatabaseBatchInteractor databaseBatchInteractor;
public static KeyWritableRepository create(Context context) { public static KeyWritableRepository create(Context context) {
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
@@ -116,6 +114,7 @@ public class KeyWritableRepository extends KeyRepository {
this.context = context; this.context = context;
this.databaseNotifyManager = databaseNotifyManager; this.databaseNotifyManager = databaseNotifyManager;
this.autocryptPeerDao = autocryptPeerDao; this.autocryptPeerDao = autocryptPeerDao;
this.databaseBatchInteractor = new DatabaseBatchInteractor(getWritableDb());
} }
private LongSparseArray<CanonicalizedPublicKey> getTrustedMasterKeys() { private LongSparseArray<CanonicalizedPublicKey> getTrustedMasterKeys() {
@@ -177,27 +176,29 @@ public class KeyWritableRepository extends KeyRepository {
long masterKeyId = keyRing.getMasterKeyId(); long masterKeyId = keyRing.getMasterKeyId();
UncachedPublicKey masterKey = keyRing.getPublicKey(); UncachedPublicKey masterKey = keyRing.getPublicKey();
ArrayList<ContentProviderOperation> operations; log(LogType.MSG_IP_PREPARE);
mIndent += 1;
byte[] encodedKeyRing;
try { try {
encodedKeyRing = keyRing.getEncoded();
} catch (IOException e) {
log(LogType.MSG_IP_ENCODE_FAIL);
return SaveKeyringResult.RESULT_ERROR;
}
log(LogType.MSG_IP_PREPARE); ArrayList<BatchOp> operations = new ArrayList<>();
mIndent += 1;
// save all keys and userIds included in keyRing object in database
operations = new ArrayList<>();
try {
log(LogType.MSG_IP_INSERT_KEYRING); log(LogType.MSG_IP_INSERT_KEYRING);
try {
writePublicKeyRing(keyRing, masterKeyId, operations); byte[] encodedRingIfDbCachable = encodedKeyRing.length < MAX_CACHED_KEY_SIZE ? encodedKeyRing : null;
} catch (IOException e) { KeyRingPublic keyRingPublic = KeyRingPublic.create(masterKeyId, encodedRingIfDbCachable);
log(LogType.MSG_IP_ENCODE_FAIL); operations.add(DatabaseBatchInteractor.createInsertKeyRingPublic(keyRingPublic));
return SaveKeyringResult.RESULT_ERROR;
}
log(LogType.MSG_IP_INSERT_SUBKEYS); log(LogType.MSG_IP_INSERT_SUBKEYS);
mIndent += 1; mIndent += 1;
{ // insert subkeys { // insert subkeys
Uri uri = Keys.buildKeysUri(masterKeyId);
int rank = 0; int rank = 0;
for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) { for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) {
long keyId = key.getKeyId(); long keyId = key.getKeyId();
@@ -206,23 +207,7 @@ public class KeyWritableRepository extends KeyRepository {
); );
mIndent += 1; mIndent += 1;
ContentValues values = new ContentValues();
values.put(Keys.MASTER_KEY_ID, masterKeyId);
values.put(Keys.RANK, rank);
values.put(Keys.KEY_ID, key.getKeyId());
values.put(Keys.KEY_SIZE, key.getBitStrength());
values.put(Keys.KEY_CURVE_OID, key.getCurveOid());
values.put(Keys.ALGORITHM, key.getAlgorithm());
values.put(Keys.FINGERPRINT, key.getFingerprint());
boolean c = key.canCertify(), e = key.canEncrypt(), s = key.canSign(), a = key.canAuthenticate(); boolean c = key.canCertify(), e = key.canEncrypt(), s = key.canSign(), a = key.canAuthenticate();
values.put(Keys.CAN_CERTIFY, c);
values.put(Keys.CAN_ENCRYPT, e);
values.put(Keys.CAN_SIGN, s);
values.put(Keys.CAN_AUTHENTICATE, a);
values.put(Keys.IS_REVOKED, key.isRevoked());
values.put(Keys.IS_SECURE, key.isSecure());
// see above // see above
if (masterKeyId == keyId) { if (masterKeyId == keyId) {
@@ -240,22 +225,24 @@ public class KeyWritableRepository extends KeyRepository {
} }
Date creation = key.getCreationTime(); Date creation = key.getCreationTime();
values.put(Keys.CREATION, creation.getTime() / 1000); Date expiry = key.getExpiryTime();
Date expiryDate = key.getExpiryTime(); if (expiry != null) {
if (expiryDate != null) {
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
if (key.isExpired()) { if (key.isExpired()) {
log(keyId == masterKeyId ? log(keyId == masterKeyId ?
LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED, LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED,
expiryDate.toString()); expiry.toString());
} else { } else {
log(keyId == masterKeyId ? log(keyId == masterKeyId ?
LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES, LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES,
expiryDate.toString()); expiry.toString());
} }
} }
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); SubKey subKey = SubKey.create(masterKeyId, rank, key.getKeyId(),
key.getBitStrength(), key.getCurveOid(), key.getAlgorithm(), key.getFingerprint(),
c, s, e, a, key.isRevoked(), SecretKeyType.UNAVAILABLE, key.isSecure(), creation, expiry);
operations.add(DatabaseBatchInteractor.createInsertSubKey(subKey));
++rank; ++rank;
mIndent -= 1; mIndent -= 1;
} }
@@ -312,10 +299,8 @@ public class KeyWritableRepository extends KeyRepository {
// keep a note about the issuer of this key signature // keep a note about the issuer of this key signature
if (!signerKeyIds.contains(certId)) { if (!signerKeyIds.contains(certId)) {
operations.add(ContentProviderOperation.newInsert(KeySignatures.CONTENT_URI) KeySignature keySignature = KeySignature.create(masterKeyId, certId);
.withValue(KeySignatures.MASTER_KEY_ID, masterKeyId) operations.add(DatabaseBatchInteractor.createInsertSignerKey(keySignature));
.withValue(KeySignatures.SIGNER_KEY_ID, certId)
.build());
signerKeyIds.add(certId); signerKeyIds.add(certId);
} }
@@ -481,7 +466,10 @@ public class KeyWritableRepository extends KeyRepository {
// iterate and put into db // iterate and put into db
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
UserPacketItem item = uids.get(userIdRank); UserPacketItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); Long type = item.type != null ? item.type.longValue() : null;
UserPacket userPacket = UserPacket.create(masterKeyId, userIdRank, type, item.userId, item.name, item.email,
item.comment, item.attributeData, item.isPrimary, item.selfRevocation != null);
operations.add(DatabaseBatchInteractor.createInsertUserPacket(userPacket));
if (item.selfRevocation != null) { if (item.selfRevocation != null) {
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation, operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation,
@@ -519,9 +507,12 @@ public class KeyWritableRepository extends KeyRepository {
mIndent -= 1; mIndent -= 1;
} }
SupportSQLiteDatabase db = databaseBatchInteractor.getDb();
try { try {
db.beginTransaction();
// delete old version of this keyRing (from database only!), which also deletes all keys and userIds on cascade // delete old version of this keyRing (from database only!), which also deletes all keys and userIds on cascade
DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getWritableDb()); DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db);
deleteStatement.bind(masterKeyId); deleteStatement.bind(masterKeyId);
int deletedRows = deleteStatement.executeUpdateDelete(); int deletedRows = deleteStatement.executeUpdateDelete();
@@ -533,41 +524,25 @@ public class KeyWritableRepository extends KeyRepository {
} }
log(LogType.MSG_IP_APPLY_BATCH); log(LogType.MSG_IP_APPLY_BATCH);
contentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); databaseBatchInteractor.applyBatch(operations);
if (encodedKeyRing.length >= MAX_CACHED_KEY_SIZE) {
mLocalPublicKeyStorage.writePublicKey(masterKeyId, encodedKeyRing);
}
databaseNotifyManager.notifyKeyChange(masterKeyId); databaseNotifyManager.notifyKeyChange(masterKeyId);
db.setTransactionSuccessful();
log(LogType.MSG_IP_SUCCESS); log(LogType.MSG_IP_SUCCESS);
return result; return result;
} catch (IOException e) {
} catch (RemoteException e) {
log(LogType.MSG_IP_ERROR_REMOTE_EX);
Timber.e(e, "RemoteException during import");
return SaveKeyringResult.RESULT_ERROR;
} catch (OperationApplicationException e) {
log(LogType.MSG_IP_ERROR_OP_EXC); log(LogType.MSG_IP_ERROR_OP_EXC);
Timber.e(e, "OperationApplicationException during import"); Timber.e(e, "OperationApplicationException during import");
return SaveKeyringResult.RESULT_ERROR; return SaveKeyringResult.RESULT_ERROR;
} finally {
db.endTransaction();
} }
} }
private void writePublicKeyRing(CanonicalizedPublicKeyRing keyRing, long masterKeyId,
ArrayList<ContentProviderOperation> operations) throws IOException {
byte[] encodedKey = keyRing.getEncoded();
mLocalPublicKeyStorage.writePublicKey(masterKeyId, encodedKey);
ContentValues values = new ContentValues();
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
if (encodedKey.length < MAX_CACHED_KEY_SIZE) {
values.put(KeyRingData.KEY_RING_DATA, encodedKey);
} else {
values.put(KeyRingData.KEY_RING_DATA, (byte[]) null);
}
Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId);
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
}
private void writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException { private void writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException {
byte[] encodedKey = keyRing.getEncoded(); byte[] encodedKey = keyRing.getEncoded();
localSecretKeyStorage.writeSecretKey(masterKeyId, encodedKey); localSecretKeyStorage.writeSecretKey(masterKeyId, encodedKey);
@@ -655,12 +630,12 @@ public class KeyWritableRepository extends KeyRepository {
} }
{ {
Uri uri = Keys.buildKeysUri(masterKeyId); UpdateHasSecretByMasterKeyId resetStatement =
SubKey.createUpdateHasSecretByMasterKeyIdStatement(getWritableDb());
resetStatement.bind(masterKeyId, SecretKeyType.GNU_DUMMY);
resetStatement.executeUpdateDelete();
// first, mark all keys as not available UpdateHasSecretByKeyId updateStatement = SubKey.createUpdateHasSecretByKeyId(getWritableDb());
ContentValues values = new ContentValues();
values.put(Keys.HAS_SECRET, SecretKeyType.GNU_DUMMY.getNum());
contentResolver.update(uri, values, null, null);
// then, mark exactly the keys we have available // then, mark exactly the keys we have available
log(LogType.MSG_IS_IMPORTING_SUBKEYS); log(LogType.MSG_IS_IMPORTING_SUBKEYS);
@@ -668,36 +643,25 @@ public class KeyWritableRepository extends KeyRepository {
for (CanonicalizedSecretKey sub : keyRing.secretKeyIterator()) { for (CanonicalizedSecretKey sub : keyRing.secretKeyIterator()) {
long id = sub.getKeyId(); long id = sub.getKeyId();
SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive(); SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive();
values.put(Keys.HAS_SECRET, mode.getNum()); updateStatement.bind(id, mode);
int upd = contentResolver.update(uri, values, Keys.KEY_ID + " = ?", int upd = updateStatement.executeUpdateDelete();
new String[]{Long.toString(id)});
if (upd == 1) { if (upd == 1) {
switch (mode) { switch (mode) {
case PASSPHRASE: case PASSPHRASE:
log(LogType.MSG_IS_SUBKEY_OK, log(LogType.MSG_IS_SUBKEY_OK, KeyFormattingUtils.convertKeyIdToHex(id));
KeyFormattingUtils.convertKeyIdToHex(id)
);
break; break;
case PASSPHRASE_EMPTY: case PASSPHRASE_EMPTY:
log(LogType.MSG_IS_SUBKEY_EMPTY, log(LogType.MSG_IS_SUBKEY_EMPTY, KeyFormattingUtils.convertKeyIdToHex(id));
KeyFormattingUtils.convertKeyIdToHex(id)
);
break; break;
case GNU_DUMMY: case GNU_DUMMY:
log(LogType.MSG_IS_SUBKEY_STRIPPED, log(LogType.MSG_IS_SUBKEY_STRIPPED, KeyFormattingUtils.convertKeyIdToHex(id));
KeyFormattingUtils.convertKeyIdToHex(id)
);
break; break;
case DIVERT_TO_CARD: case DIVERT_TO_CARD:
log(LogType.MSG_IS_SUBKEY_DIVERT, log(LogType.MSG_IS_SUBKEY_DIVERT, KeyFormattingUtils.convertKeyIdToHex(id));
KeyFormattingUtils.convertKeyIdToHex(id)
);
break; break;
} }
} else { } else {
log(LogType.MSG_IS_SUBKEY_NONEXISTENT, log(LogType.MSG_IS_SUBKEY_NONEXISTENT, KeyFormattingUtils.convertKeyIdToHex(id));
KeyFormattingUtils.convertKeyIdToHex(id)
);
} }
} }
mIndent -= 1; mIndent -= 1;
@@ -744,7 +708,7 @@ public class KeyWritableRepository extends KeyRepository {
// If there is an old keyring, merge it // If there is an old keyring, merge it
try { try {
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing(); UncachedKeyRing oldPublicRing = UncachedKeyRing.decodeFromData(loadPublicKeyRingData(masterKeyId));
alreadyExists = true; alreadyExists = true;
// Merge data from new public ring into the old one // Merge data from new public ring into the old one
@@ -769,7 +733,7 @@ public class KeyWritableRepository extends KeyRepository {
log(LogType.MSG_IP_SUCCESS_IDENTICAL); log(LogType.MSG_IP_SUCCESS_IDENTICAL);
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, canPublicRing); return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, canPublicRing);
} }
} catch (NotFoundException e) { } catch (PgpGeneralException | NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring. // Not an issue, just means we are dealing with a new keyring.
// Canonicalize this keyring, to assert a number of assumptions made about it. // Canonicalize this keyring, to assert a number of assumptions made about it.
@@ -1038,54 +1002,20 @@ public class KeyWritableRepository extends KeyRepository {
} }
} }
if (!isTrustDbInitialized) { preferences.setKeySignaturesTableInitialized();
preferences.setKeySignaturesTableInitialized();
}
log.add(LogType.MSG_TRUST_OK, 1); log.add(LogType.MSG_TRUST_OK, 1);
return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log); return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log);
} }
/** private BatchOp buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus) {
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing try {
*/ Certification certification = Certification.create(masterKeyId, rank, cert.getKeyId(),
private ContentProviderOperation cert.getSignatureType(), verificationStatus, cert.getCreationTime(), cert.getEncoded());
buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus) return DatabaseBatchInteractor.createInsertCertification(certification);
throws IOException { } catch (IOException e) {
ContentValues values = new ContentValues(); throw new AssertionError(e);
values.put(Certs.MASTER_KEY_ID, masterKeyId); }
values.put(Certs.RANK, rank);
values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyId());
values.put(Certs.TYPE, cert.getSignatureType());
values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000);
values.put(Certs.VERIFIED, CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.encode(verificationStatus));
values.put(Certs.DATA, cert.getEncoded());
Uri uri = Certs.buildCertsUri(masterKeyId);
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
/**
* Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
*/
private ContentProviderOperation
buildUserIdOperations(long masterKeyId, UserPacketItem item, int rank) {
ContentValues values = new ContentValues();
values.put(UserPackets.MASTER_KEY_ID, masterKeyId);
values.put(UserPackets.TYPE, item.type);
values.put(UserPackets.USER_ID, item.userId);
values.put(UserPackets.NAME, item.name);
values.put(UserPackets.EMAIL, item.email);
values.put(UserPackets.COMMENT, item.comment);
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
values.put(UserPackets.IS_PRIMARY, item.isPrimary);
values.put(UserPackets.IS_REVOKED, item.selfRevocation != null);
values.put(UserPackets.RANK, rank);
Uri uri = UserPackets.buildUserIdsUri(masterKeyId);
return ContentProviderOperation.newInsert(uri).withValues(values).build();
} }
} }

View File

@@ -0,0 +1,44 @@
package org.sufficientlysecure.keychain.daos;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.database.Cursor;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.model.UserPacket;
import org.sufficientlysecure.keychain.model.UserPacket.UidStatus;
public class UserIdDao extends AbstractDao {
public static UserIdDao getInstance(Context context) {
KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new UserIdDao(keychainDatabase, databaseNotifyManager);
}
private UserIdDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) {
super(db, databaseNotifyManager);
}
public UidStatus getUidStatusByEmailLike(String emailLike) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmailLike(emailLike);
return mapSingleRow(query, UserPacket.UID_STATUS_MAPPER);
}
public Map<String,UidStatus> getUidStatusByEmail(String... emails) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmail(emails);
Map<String,UidStatus> result = new HashMap<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UidStatus item = UserPacket.UID_STATUS_MAPPER.map(cursor);
result.put(item.email(), item);
}
}
return result;
}
}

View File

@@ -1,8 +1,13 @@
package org.sufficientlysecure.keychain.model; package org.sufficientlysecure.keychain.model;
import java.util.Date;
import android.arch.persistence.db.SupportSQLiteDatabase;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.CertsModel; import org.sufficientlysecure.keychain.CertsModel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
@AutoValue @AutoValue
@@ -13,6 +18,20 @@ public abstract class Certification implements CertsModel {
public static final SelectVerifyingCertDetailsMapper<CertDetails> CERT_DETAILS_MAPPER = public static final SelectVerifyingCertDetailsMapper<CertDetails> CERT_DETAILS_MAPPER =
new SelectVerifyingCertDetailsMapper<>(AutoValue_Certification_CertDetails::new); new SelectVerifyingCertDetailsMapper<>(AutoValue_Certification_CertDetails::new);
public static Certification create(long masterKeyId, long rank, long keyIdCertifier, long type,
VerificationStatus verified, Date creation, byte[] data) {
long creationUnixTime = creation.getTime() / 1000;
return new AutoValue_Certification(masterKeyId, rank, keyIdCertifier, type, verified, creationUnixTime, data);
}
public static InsertCert createInsertStatement(SupportSQLiteDatabase db) {
return new InsertCert(db, FACTORY);
}
public void bindTo(InsertCert statement) {
statement.bind(master_key_id(), rank(), key_id_certifier(), type(), verified(), creation(), data());
}
@AutoValue @AutoValue
public static abstract class CertDetails implements CertsModel.SelectVerifyingCertDetailsModel { public static abstract class CertDetails implements CertsModel.SelectVerifyingCertDetailsModel {

View File

@@ -1,12 +1,28 @@
package org.sufficientlysecure.keychain.model; package org.sufficientlysecure.keychain.model;
import android.arch.persistence.db.SupportSQLiteDatabase;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.KeyRingsPublicModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel;
import org.sufficientlysecure.keychain.KeysModel.InsertKey;
@AutoValue @AutoValue
public abstract class KeyRingPublic implements KeyRingsPublicModel { public abstract class KeyRingPublic implements KeyRingsPublicModel {
public static final Factory<KeyRingPublic> FACTORY = new Factory<>(AutoValue_KeyRingPublic::new); public static final Factory<KeyRingPublic> FACTORY = new Factory<>(AutoValue_KeyRingPublic::new);
public static final Mapper<KeyRingPublic> MAPPER = new Mapper<>(FACTORY); public static final Mapper<KeyRingPublic> MAPPER = new Mapper<>(FACTORY);
public static KeyRingPublic create(long masterKeyId, byte[] keyRingData) {
return new AutoValue_KeyRingPublic(masterKeyId, keyRingData);
}
public static InsertKeyRingPublic createInsertStatement(SupportSQLiteDatabase db) {
return new InsertKeyRingPublic(db);
}
public void bindTo(InsertKeyRingPublic statement) {
statement.bind(master_key_id(), key_ring_data());
}
} }

View File

@@ -1,6 +1,8 @@
package org.sufficientlysecure.keychain.model; package org.sufficientlysecure.keychain.model;
import android.arch.persistence.db.SupportSQLiteDatabase;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.KeySignaturesModel; import org.sufficientlysecure.keychain.KeySignaturesModel;
@@ -10,4 +12,16 @@ public abstract class KeySignature implements KeySignaturesModel {
public static final Factory<KeySignature> FACTORY = new Factory<>(AutoValue_KeySignature::new); public static final Factory<KeySignature> FACTORY = new Factory<>(AutoValue_KeySignature::new);
public static final Mapper<KeySignature> MAPPER = new Mapper<>(FACTORY); public static final Mapper<KeySignature> MAPPER = new Mapper<>(FACTORY);
public static InsertKeySignature createInsertStatement(SupportSQLiteDatabase db) {
return new InsertKeySignature(db);
}
public void bindTo(InsertKeySignature statement) {
statement.bind(master_key_id(), signer_key_id());
}
public static KeySignature create(long masterKeyId, long certId) {
return null;
}
} }

View File

@@ -3,8 +3,11 @@ package org.sufficientlysecure.keychain.model;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.squareup.sqldelight.RowMapper; import com.squareup.sqldelight.RowMapper;
import org.sufficientlysecure.keychain.KeysModel; import org.sufficientlysecure.keychain.KeysModel;
@@ -26,6 +29,33 @@ public abstract class SubKey implements KeysModel {
return expiry() != null; return expiry() != null;
} }
public static SubKey create(long masterKeyId, long rank, long keyId, Integer keySize, String keyCurveOid,
int algorithm, byte[] fingerprint, boolean canCertify, boolean canSign, boolean canEncrypt, boolean canAuth,
boolean isRevoked, SecretKeyType hasSecret, boolean isSecure, Date creation, Date expiry) {
long creationUnixTime = creation.getTime() / 1000;
Long expiryUnixTime = expiry != null ? expiry.getTime() / 1000 : null;
return new AutoValue_SubKey(masterKeyId, rank, keyId, keySize, keyCurveOid, algorithm, fingerprint, canCertify,
canSign, canEncrypt, canAuth, isRevoked, hasSecret, isSecure, creationUnixTime, expiryUnixTime);
}
public static InsertKey createInsertStatement(SupportSQLiteDatabase db) {
return new InsertKey(db, FACTORY);
}
public static UpdateHasSecretByMasterKeyId createUpdateHasSecretByMasterKeyIdStatement(SupportSQLiteDatabase db) {
return new UpdateHasSecretByMasterKeyId(db, FACTORY);
}
public static UpdateHasSecretByKeyId createUpdateHasSecretByKeyId(SupportSQLiteDatabase db) {
return new UpdateHasSecretByKeyId(db, FACTORY);
}
public void bindTo(InsertKey statement) {
statement.bind(master_key_id(), rank(), key_id(), key_size(), key_curve_oid(), algorithm(), fingerprint(),
can_certify(), can_sign(), can_encrypt(), can_authenticate(), is_revoked(), has_secret(), is_secure(),
creation(), expiry());
}
@AutoValue @AutoValue
public static abstract class UnifiedKeyInfo implements KeysModel.UnifiedKeyViewModel { public static abstract class UnifiedKeyInfo implements KeysModel.UnifiedKeyViewModel {
private List<String> autocryptPackageNames; private List<String> autocryptPackageNames;

View File

@@ -1,12 +1,12 @@
package org.sufficientlysecure.keychain.model; package org.sufficientlysecure.keychain.model;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.UserPacketsModel; import org.sufficientlysecure.keychain.UserPacketsModel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
@AutoValue @AutoValue
@@ -16,6 +16,23 @@ public abstract class UserPacket implements UserPacketsModel {
FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new); FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new);
public static final SelectUserAttributesByTypeAndMasterKeyIdMapper<UserAttribute> USER_ATTRIBUTE_MAPPER = public static final SelectUserAttributesByTypeAndMasterKeyIdMapper<UserAttribute> USER_ATTRIBUTE_MAPPER =
FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new); FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new);
public static final UidStatusMapper<UidStatus> UID_STATUS_MAPPER =
FACTORY.selectUserIdStatusByEmailMapper(AutoValue_UserPacket_UidStatus::new);
public static UserPacket create(long masterKeyId, int rank, Long type, String userId, String name, String email,
String comment, byte[] attribute_data, boolean isPrimary, boolean isRevoked) {
return new AutoValue_UserPacket(masterKeyId, rank, type,
userId, name, email, comment, attribute_data, isPrimary, isRevoked);
}
public static InsertUserPacket createInsertStatement(SupportSQLiteDatabase db) {
return new InsertUserPacket(db);
}
public void bindTo(InsertUserPacket statement) {
statement.bind(master_key_id(), rank(), type(), user_id(), name(), email(), comment(), attribute_data(),
is_primary(), is_revoked());
}
@AutoValue @AutoValue
public static abstract class UserId implements SelectUserIdsByMasterKeyIdModel { public static abstract class UserId implements SelectUserIdsByMasterKeyIdModel {
@@ -40,4 +57,11 @@ public abstract class UserPacket implements UserPacketsModel {
return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified_int()); return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified_int());
} }
} }
@AutoValue
public static abstract class UidStatus implements UidStatusModel {
public VerificationStatus keyStatus() {
return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(key_status_int());
}
}
} }

View File

@@ -339,7 +339,6 @@ public abstract class OperationResult implements Parcelable {
MSG_IP_ENCODE_FAIL (LogLevel.DEBUG, R.string.msg_ip_encode_fail), MSG_IP_ENCODE_FAIL (LogLevel.DEBUG, R.string.msg_ip_encode_fail),
MSG_IP_ERROR_IO_EXC (LogLevel.ERROR, R.string.msg_ip_error_io_exc), MSG_IP_ERROR_IO_EXC (LogLevel.ERROR, R.string.msg_ip_error_io_exc),
MSG_IP_ERROR_OP_EXC (LogLevel.ERROR, R.string.msg_ip_error_op_exc), MSG_IP_ERROR_OP_EXC (LogLevel.ERROR, R.string.msg_ip_error_op_exc),
MSG_IP_ERROR_REMOTE_EX (LogLevel.ERROR, R.string.msg_ip_error_remote_ex),
MSG_IP_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_ip_fingerprint_error), MSG_IP_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_ip_fingerprint_error),
MSG_IP_FINGERPRINT_OK (LogLevel.INFO, R.string.msg_ip_fingerprint_ok), MSG_IP_FINGERPRINT_OK (LogLevel.INFO, R.string.msg_ip_fingerprint_ok),
MSG_IP_INSERT_KEYRING (LogLevel.DEBUG, R.string.msg_ip_insert_keyring), MSG_IP_INSERT_KEYRING (LogLevel.DEBUG, R.string.msg_ip_insert_keyring),

View File

@@ -17,18 +17,11 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
public class KeychainContract { public class KeychainContract {
public interface KeyRingsColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id
String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
}
public interface KeysColumns { public interface KeysColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id String MASTER_KEY_ID = "master_key_id"; // not a database id
String RANK = "rank"; String RANK = "rank";
@@ -51,11 +44,6 @@ public class KeychainContract {
String EXPIRY = "expiry"; String EXPIRY = "expiry";
} }
public interface KeySignaturesColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id
String SIGNER_KEY_ID = "signer_key_id";
}
public interface UserPacketsColumns { public interface UserPacketsColumns {
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
String TYPE = "type"; // not a database id String TYPE = "type"; // not a database id
@@ -79,78 +67,15 @@ public class KeychainContract {
String DATA = "data"; String DATA = "data";
} }
public interface ApiAppsAllowedKeysColumns {
String KEY_ID = "key_id"; // not a database id
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
}
public interface OverriddenWarnings {
String IDENTIFIER = "identifier";
}
public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
.parse("content://" + CONTENT_AUTHORITY);
public static final String BASE_KEY_RINGS = "key_rings";
public static final String BASE_KEY_SIGNATURES = "key_signatures";
public static final String PATH_PUBLIC = "public";
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys";
public static final String PATH_CERTS = "certs";
public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
}
public static class KeyRingData implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static Uri buildPublicKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build();
}
}
public static class Keys implements KeysColumns, BaseColumns { public static class Keys implements KeysColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static Uri buildKeysUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build();
}
}
public static class KeySignatures implements KeySignaturesColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_SIGNATURES).build();
} }
public static class UserPackets implements UserPacketsColumns, BaseColumns { public static class UserPackets implements UserPacketsColumns, BaseColumns {
public static final String VERIFIED = "verified";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static Uri buildUserIdsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build();
}
} }
public static class Certs implements CertsColumns, BaseColumns { public static class Certs implements CertsColumns, BaseColumns {
public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SECRET = 1;
public static final int VERIFIED_SELF = 2; public static final int VERIFIED_SELF = 2;
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static Uri buildCertsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build();
}
} }
private KeychainContract() { private KeychainContract() {

View File

@@ -38,19 +38,6 @@ public class KeychainExternalContract {
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;
public static class EmailStatus implements BaseColumns {
public static final String EMAIL_ADDRESS = "email_address";
public static final String USER_ID = "user_id";
public static final String USER_ID_STATUS = "email_status";
public static final String MASTER_KEY_ID = "master_key_id";
public static final 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.email_status";
}
public static class AutocryptStatus implements BaseColumns { public static class AutocryptStatus implements BaseColumns {
public static final String ADDRESS = "address"; public static final String ADDRESS = "address";
@@ -72,9 +59,6 @@ public class KeychainExternalContract {
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_STATUS).build(); .appendPath(BASE_AUTOCRYPT_STATUS).build();
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status";
} }
private KeychainExternalContract() { private KeychainExternalContract() {

View File

@@ -1,225 +1,48 @@
/*
* 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; package org.sufficientlysecure.keychain.provider;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.KeychainDatabase.Tables;
import timber.log.Timber;
public class KeychainProvider extends ContentProvider { public class KeychainProvider extends ContentProvider {
private static final int KEY_RING_KEYS = 201;
private static final int KEY_RING_USER_IDS = 202;
private static final int KEY_RING_PUBLIC = 203;
private static final int KEY_RING_CERTS = 205;
private static final int KEY_SIGNATURES = 700;
protected UriMatcher mUriMatcher;
/**
* Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by
* this {@link ContentProvider}.
*/
protected UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
String authority = KeychainContract.CONTENT_AUTHORITY;
/*
* list key_ring specifics
*
* <pre>
* key_rings/_/keys
* key_rings/_/user_ids
* key_rings/_/public
* key_rings/_/certs
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_KEYS,
KEY_RING_KEYS);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_USER_IDS,
KEY_RING_USER_IDS);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_PUBLIC,
KEY_RING_PUBLIC);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_CERTS,
KEY_RING_CERTS);
matcher.addURI(authority, KeychainContract.BASE_KEY_SIGNATURES, KEY_SIGNATURES);
return matcher;
}
private KeychainDatabase mKeychainDatabase;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
mUriMatcher = buildUriMatcher(); return false;
return true;
} }
public KeychainDatabase getDb() { @Nullable
if(mKeychainDatabase == null) { @Override
mKeychainDatabase = KeychainDatabase.getInstance(getContext()); public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
} @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return mKeychainDatabase; throw new UnsupportedOperationException();
} }
@Nullable
@Override @Override
public String getType(@NonNull Uri uri) { public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Nullable
@Override @Override
public Cursor query( public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")");
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
Uri rowUri = null;
Long keyId = null;
try {
final int match = mUriMatcher.match(uri);
switch (match) {
case KEY_RING_PUBLIC: {
db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
}
case KEY_RING_KEYS: {
db.insert(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
}
case KEY_RING_USER_IDS: {
// iff TYPE is null, user_id MUST be null as well
if (!(values.get(UserPacketsColumns.TYPE) == null
? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null)
: (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null)
)) {
throw new AssertionError("Incorrect type for user packet! This is a bug!");
}
if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insert(Tables.USER_PACKETS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
}
case KEY_RING_CERTS: {
// we replace here, keeping only the latest signature
// TODO this would be better handled in savePublicKeyRing directly!
db.insert(Tables.CERTS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
break;
}
case KEY_SIGNATURES: {
db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values);
rowUri = KeySignatures.CONTENT_URI;
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (keyId != null) {
uri = DatabaseNotifyManager.getNotifyUriMasterKeyId(keyId);
rowUri = uri;
}
} catch (SQLiteConstraintException e) {
Timber.d(e, "Constraint exception on insert! Entry already existing?");
}
return rowUri;
}
@Override
public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")"); throw new UnsupportedOperationException();
}
final SupportSQLiteDatabase db = getDb().getWritableDatabase(); @Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
int count = 0; @Nullable String[] selectionArgs) {
try { throw new UnsupportedOperationException();
final int match = mUriMatcher.match(uri);
switch (match) {
case KEY_RING_KEYS: {
if (values.size() != 1 || !values.containsKey(Keys.HAS_SECRET)) {
throw new UnsupportedOperationException(
"Only has_secret column may be updated!");
}
// make sure we get a long value here
Long mkid = Long.parseLong(uri.getPathSegments().get(1));
String actualSelection = Keys.MASTER_KEY_ID + " = " + Long.toString(mkid);
if (!TextUtils.isEmpty(selection)) {
actualSelection += " AND (" + selection + ")";
}
count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
} catch (SQLiteConstraintException e) {
Timber.d(e, "Constraint exception on update! Entry already existing?");
}
return count;
} }
} }

View File

@@ -3,8 +3,11 @@ package org.sufficientlysecure.keychain.remote;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -146,15 +149,15 @@ public class AutocryptInteractor {
return uncachedKeyRing; return uncachedKeyRing;
} }
public List<AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) { public Map<String,AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
List<AutocryptRecommendationResult> result = new ArrayList<>(autocryptIds.length); Map<String,AutocryptRecommendationResult> result = new HashMap<>(autocryptIds.length);
for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) { for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) {
AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus); AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus);
result.add(peerResult); result.put(peerResult.peerId, peerResult);
} }
return result; return Collections.unmodifiableMap(result);
} }
/** Determines Autocrypt "ui-recommendation", according to spec. /** Determines Autocrypt "ui-recommendation", according to spec.

View File

@@ -20,34 +20,28 @@ package org.sufficientlysecure.keychain.remote;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.UriMatcher; import android.content.UriMatcher;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils; import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.widget.Toast;
import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.daos.UserIdDao;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.model.UserPacket.UidStatus;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult;
import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState;
import timber.log.Timber; import timber.log.Timber;
@@ -59,39 +53,23 @@ public class KeychainExternalProvider extends ContentProvider {
private static final int AUTOCRYPT_STATUS = 201; private static final int AUTOCRYPT_STATUS = 201;
private static final int AUTOCRYPT_STATUS_INTERNAL = 202; private static final int AUTOCRYPT_STATUS_INTERNAL = 202;
public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses";
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
private UriMatcher uriMatcher; private UriMatcher uriMatcher;
private ApiPermissionHelper apiPermissionHelper; private ApiPermissionHelper apiPermissionHelper;
/**
* Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by
* this {@link ContentProvider}.
*/
protected UriMatcher buildUriMatcher() { protected UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
String authority = KeychainExternalContract.CONTENT_AUTHORITY_EXTERNAL; String authority = KeychainExternalContract.CONTENT_AUTHORITY_EXTERNAL;
/*
* list email_status
*
* <pre>
* email_status/
* </pre>
*/
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS);
matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_STATUS, AUTOCRYPT_STATUS); matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_STATUS, AUTOCRYPT_STATUS);
matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_STATUS + "/*", AUTOCRYPT_STATUS_INTERNAL); matcher.addURI(authority, KeychainExternalContract.BASE_AUTOCRYPT_STATUS + "/*", AUTOCRYPT_STATUS_INTERNAL);
return matcher; return matcher;
} }
/** {@inheritDoc} */
@Override @Override
public boolean onCreate() { public boolean onCreate() {
uriMatcher = buildUriMatcher(); uriMatcher = buildUriMatcher();
@@ -105,104 +83,22 @@ public class KeychainExternalProvider extends ContentProvider {
return true; return true;
} }
/**
* {@inheritDoc}
*/
@Override
public String getType(@NonNull Uri uri) {
final int match = uriMatcher.match(uri);
switch (match) {
case EMAIL_STATUS:
return EmailStatus.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
/**
* {@inheritDoc}
*/
@Override @Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) { String sortOrder) {
Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
long startTime = System.currentTimeMillis(); Context context = getContext();
if (context == null) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); throw new IllegalStateException();
}
int match = uriMatcher.match(uri);
String groupBy = null;
SupportSQLiteDatabase db = KeychainDatabase.getTemporaryInstance(getContext()).getReadableDatabase();
String callingPackageName = apiPermissionHelper.getCurrentCallingPackage(); String callingPackageName = apiPermissionHelper.getCurrentCallingPackage();
int match = uriMatcher.match(uri);
switch (match) { switch (match) {
case EMAIL_STATUS: { case EMAIL_STATUS: {
boolean callerIsAllowed = apiPermissionHelper.isAllowedIgnoreErrors(); Toast.makeText(context, "This API is no longer supported by OpenKeychain!", Toast.LENGTH_SHORT).show();
if (!callerIsAllowed) { return new MatrixCursor(projection);
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, SQLiteDatabase.CONFLICT_FAIL, cv);
}
HashMap<String, String> projectionMap = new HashMap<>();
projectionMap.put(EmailStatus._ID, "email AS _id");
projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS);
projectionMap.put(EmailStatus.USER_ID,
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
// we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified"
projectionMap.put(EmailStatus.USER_ID_STATUS, "CASE ( MIN (" + Certs.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 " + KeychainExternalContract.KEY_STATUS_UNVERIFIED
+ " END AS " + EmailStatus.USER_ID_STATUS);
projectionMap.put(EmailStatus.MASTER_KEY_ID,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID);
qb.setProjectionMap(projectionMap);
if (projection == null) {
throw new IllegalArgumentException("Please provide a projection!");
}
qb.setTables(
TEMP_TABLE_QUERIED_ADDRESSES
+ " LEFT JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL"
+ " AND " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " LIKE " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES
+ ")"
+ " LEFT JOIN " + Tables.CERTS + " ON ("
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID
+ " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + Tables.CERTS + "." + 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;
}
// verified == 0 has no self-cert, which is basically an error case. never return that!
// verified == null is fine, because it means there was no join partner
qb.appendWhere(Tables.CERTS + "." + Certs.VERIFIED + " IS NULL OR " + Tables.CERTS + "." + Certs.VERIFIED + " > 0");
if (TextUtils.isEmpty(sortOrder)) {
sortOrder = EmailStatus.EMAIL_ADDRESS;
}
// uri to watch is all /key_rings/
uri = KeyRings.CONTENT_URI;
break;
} }
case AUTOCRYPT_STATUS_INTERNAL: case AUTOCRYPT_STATUS_INTERNAL:
@@ -223,164 +119,127 @@ public class KeychainExternalProvider extends ContentProvider {
throw new IllegalArgumentException("Please provide a projection!"); throw new IllegalArgumentException("Please provide a projection!");
} }
db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" +
TEMP_TABLE_COLUMN_ADDRES + " TEXT NOT NULL PRIMARY KEY, " +
AutocryptStatus.UID_KEY_STATUS + " INT, " +
AutocryptStatus.UID_ADDRESS + " TEXT, " +
AutocryptStatus.UID_MASTER_KEY_ID + " INT, " +
AutocryptStatus.UID_CANDIDATES + " INT, " +
AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT DEFAULT " + AutocryptStatus.AUTOCRYPT_PEER_DISABLED + ", " +
AutocryptStatus.AUTOCRYPT_KEY_STATUS + " INT, " +
AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + " INT" +
");");
ContentValues cv = new ContentValues();
for (String address : selectionArgs) {
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
}
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
List<String> plist = Arrays.asList(projection); List<String> plist = Arrays.asList(projection);
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
boolean queriesUidResult = plist.contains(AutocryptStatus.UID_KEY_STATUS) || boolean queriesUidResult = plist.contains(AutocryptStatus.UID_KEY_STATUS) ||
plist.contains(AutocryptStatus.UID_ADDRESS) || plist.contains(AutocryptStatus.UID_ADDRESS) ||
plist.contains(AutocryptStatus.UID_MASTER_KEY_ID) || plist.contains(AutocryptStatus.UID_MASTER_KEY_ID) ||
plist.contains(AutocryptStatus.UID_CANDIDATES); plist.contains(AutocryptStatus.UID_CANDIDATES);
if (queriesUidResult) {
fillTempTableWithUidResult(db, isWildcardSelector);
}
boolean queriesAutocryptResult = plist.contains(AutocryptStatus.AUTOCRYPT_PEER_STATE) || boolean queriesAutocryptResult = plist.contains(AutocryptStatus.AUTOCRYPT_PEER_STATE) ||
plist.contains(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID) || plist.contains(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID) ||
plist.contains(AutocryptStatus.AUTOCRYPT_KEY_STATUS); plist.contains(AutocryptStatus.AUTOCRYPT_KEY_STATUS);
if (isWildcardSelector && queriesAutocryptResult) { if (isWildcardSelector && queriesAutocryptResult) {
throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!"); throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!");
} }
if (!isWildcardSelector && queriesAutocryptResult) {
AutocryptInteractor autocryptInteractor =
AutocryptInteractor.getInstance(getContext(), callingPackageName);
fillTempTableWithAutocryptRecommendations(db, autocryptInteractor, selectionArgs);
}
HashMap<String, String> projectionMap = new HashMap<>(); Map<String, UidStatus> uidStatuses = queriesUidResult ?
projectionMap.put(AutocryptStatus._ID, AutocryptStatus._ID); loadUidStatusMap(selectionArgs, isWildcardSelector) : Collections.emptyMap();
projectionMap.put(AutocryptStatus.ADDRESS, AutocryptStatus.ADDRESS); Map<String, AutocryptRecommendationResult> autocryptStates = queriesAutocryptResult ?
projectionMap.put(AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_KEY_STATUS); loadAutocryptRecommendationMap(selectionArgs, callingPackageName) : Collections.emptyMap();
projectionMap.put(AutocryptStatus.UID_ADDRESS, AutocryptStatus.UID_ADDRESS);
projectionMap.put(AutocryptStatus.UID_MASTER_KEY_ID, AutocryptStatus.UID_MASTER_KEY_ID);
projectionMap.put(AutocryptStatus.UID_CANDIDATES, AutocryptStatus.UID_CANDIDATES);
projectionMap.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_PEER_STATE);
projectionMap.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_KEY_STATUS);
projectionMap.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID);
qb.setProjectionMap(projectionMap);
qb.setTables(TEMP_TABLE_QUERIED_ADDRESSES);
if (TextUtils.isEmpty(sortOrder)) { MatrixCursor cursor =
sortOrder = AutocryptStatus.ADDRESS; mapResultsToProjectedMatrixCursor(projection, selectionArgs, uidStatuses, autocryptStates);
}
// uri to watch is all /key_rings/ uri = DatabaseNotifyManager.getNotifyUriAllKeys();
uri = KeyRings.CONTENT_URI; cursor.setNotificationUri(context.getContentResolver(), uri);
break;
return cursor;
} }
default: { default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
} }
} }
}
// If no sort order is specified use the default @NonNull
String orderBy; private MatrixCursor mapResultsToProjectedMatrixCursor(String[] projection, String[] selectionArgs,
if (TextUtils.isEmpty(sortOrder)) { Map<String, UidStatus> uidStatuses, Map<String, AutocryptRecommendationResult> autocryptStates) {
orderBy = null; MatrixCursor cursor = new MatrixCursor(projection);
} else { for (String selectionArg : selectionArgs) {
orderBy = sortOrder; AutocryptRecommendationResult autocryptResult = autocryptStates.get(selectionArg);
} UidStatus uidStatus = uidStatuses.get(selectionArg);
qb.setStrict(true); Object[] row = new Object[projection.length];
String query = qb.buildQuery(projection, null, groupBy, null, orderBy, null); for (int i = 0; i < projection.length; i++) {
Cursor cursor = db.query(query); if (AutocryptStatus.ADDRESS.equals(projection[i]) || AutocryptStatus._ID.equals(projection[i])) {
if (cursor != null) { row[i] = selectionArg;
// Tell the cursor what uri to watch, so it knows when its source data changes } else {
cursor.setNotificationUri(getContext().getContentResolver(), uri); row[i] = columnNameToRowContent(projection[i], autocryptResult, uidStatus);
if (Constants.DEBUG_LOG_DB_QUERIES) { }
DatabaseUtils.dumpCursor(cursor);
} }
cursor.addRow(row);
} }
Timber.d("Query: " + qb.buildQuery(projection, selection, groupBy, null, orderBy, null));
Timber.d(Constants.TAG, "Query took %s ms", (System.currentTimeMillis() - startTime));
return cursor; return cursor;
} }
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, private Object columnNameToRowContent(
AutocryptInteractor autocryptInteractor, String[] peerIds) { String columnName, AutocryptRecommendationResult autocryptResult, UidStatus uidStatus) {
List<AutocryptRecommendationResult> autocryptStates = switch (columnName) {
autocryptInteractor.determineAutocryptRecommendations(peerIds); case AutocryptStatus.UID_KEY_STATUS: {
if (uidStatus == null) {
fillTempTableWithAutocryptRecommendations(db, autocryptStates); return null;
} }
return uidStatus.keyStatus() == VerificationStatus.VERIFIED_SECRET ?
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
List<AutocryptRecommendationResult> autocryptRecommendations) {
ContentValues cv = new ContentValues();
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
cv.clear();
cv.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, getPeerStateValue(peerResult.autocryptState));
if (peerResult.masterKeyId != null) {
cv.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, peerResult.masterKeyId);
cv.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, peerResult.isVerified ?
KeychainExternalContract.KEY_STATUS_VERIFIED : KeychainExternalContract.KEY_STATUS_VERIFIED :
KeychainExternalContract.KEY_STATUS_UNVERIFIED); KeychainExternalContract.KEY_STATUS_UNVERIFIED;
} }
case AutocryptStatus.UID_ADDRESS:
if (uidStatus == null) {
return null;
}
return uidStatus.user_id();
db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", case AutocryptStatus.UID_MASTER_KEY_ID:
new String[] { peerResult.peerId }); if (uidStatus == null) {
return null;
}
return uidStatus.master_key_id();
case AutocryptStatus.UID_CANDIDATES:
if (uidStatus == null) {
return null;
}
return uidStatus.candidates();
case AutocryptStatus.AUTOCRYPT_PEER_STATE:
if (autocryptResult == null) {
return null;
}
return getPeerStateValue(autocryptResult.autocryptState);
case AutocryptStatus.AUTOCRYPT_KEY_STATUS:
if (autocryptResult == null) {
return null;
}
return autocryptResult.isVerified ?
KeychainExternalContract.KEY_STATUS_VERIFIED : KeychainExternalContract.KEY_STATUS_UNVERIFIED;
case AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID:
if (autocryptResult == null) {
return null;
}
return autocryptResult.masterKeyId;
default:
throw new IllegalArgumentException("Unhandled case " + columnName);
} }
} }
private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) { private Map<String, UidStatus> loadUidStatusMap(String[] selectionArgs, boolean isWildcardSelector) {
String cmpOperator = isWildcardSelector ? " LIKE " : " = "; UserIdDao userIdDao = UserIdDao.getInstance(getContext());
long unixSeconds = System.currentTimeMillis() / 1000; if (isWildcardSelector) {
db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + UidStatus uidStatus = userIdDao.getUidStatusByEmailLike(selectionArgs[0]);
"(" + TEMP_TABLE_COLUMN_ADDRES + ", " + AutocryptStatus.UID_KEY_STATUS + ", " + return Collections.singletonMap(selectionArgs[0], uidStatus);
AutocryptStatus.UID_ADDRESS + ", " + AutocryptStatus.UID_MASTER_KEY_ID } else {
+ ", " + AutocryptStatus.UID_CANDIDATES + ")" + return userIdDao.getUidStatusByEmail(selectionArgs);
" SELECT " + TEMP_TABLE_COLUMN_ADDRES + ", " + }
"CASE ( MIN (" + Tables.CERTS + "." + Certs.VERIFIED + " ) ) " }
// remap to keep this provider contract independent from our internal representation
+ " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED private Map<String, AutocryptRecommendationResult> loadAutocryptRecommendationMap(
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED String[] selectionArgs, String callingPackageName) {
+ " END AS " + AutocryptStatus.UID_KEY_STATUS AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getContext(), callingPackageName);
+ ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID return autocryptInteractor.determineAutocryptRecommendations(selectionArgs);
+ ", " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ ", COUNT(DISTINCT " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ")"
+ " FROM " + TEMP_TABLE_QUERIED_ADDRESSES
+ " LEFT JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.USER_PACKETS + "." + UserPackets.EMAIL + cmpOperator +
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES
+ ")"
+ " LEFT JOIN " + Tables.CERTS + " ON ("
+ Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + Tables.USER_PACKETS + "." +
UserPackets.MASTER_KEY_ID
+ " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_PACKETS + "." +
UserPackets.RANK
+ " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"
+ ")"
+ " WHERE (EXISTS (SELECT 1 FROM " + Tables.KEYS + " WHERE "
+ Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.USER_PACKETS + "." +
UserPackets.MASTER_KEY_ID
+ " AND " + Tables.KEYS + "." + Keys.RANK + " = 0"
+ " AND " + Tables.KEYS + "." + Keys.IS_REVOKED + " = 0"
+ " AND NOT " + "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS +
"." + Keys.EXPIRY
+ " < " + unixSeconds + ")"
+ ")) OR " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IS NULL"
+ " GROUP BY " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES);
} }
private int getPeerStateValue(AutocryptState autocryptState) { private int getPeerStateValue(AutocryptState autocryptState) {
@@ -394,6 +253,11 @@ public class KeychainExternalProvider extends ContentProvider {
throw new IllegalStateException("Unhandled case!"); throw new IllegalStateException("Unhandled case!");
} }
@Override
public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@Override @Override
public Uri insert(@NonNull Uri uri, ContentValues values) { public Uri insert(@NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View File

@@ -52,8 +52,11 @@ import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener; import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener;
import eu.davidea.flexibleadapter.SelectableAdapter.Mode; import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.KeySyncParcel; import org.sufficientlysecure.keychain.operations.KeySyncParcel;
@@ -61,9 +64,6 @@ import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem;
@@ -492,7 +492,7 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
try { try {
KeychainDatabase.debugBackup(getActivity(), true); KeychainDatabase.debugBackup(getActivity(), true);
Notify.create(getActivity(), "Restored debug_backup.db", Notify.Style.OK).show(); Notify.create(getActivity(), "Restored debug_backup.db", Notify.Style.OK).show();
getActivity().getContentResolver().notifyChange(KeyRings.CONTENT_URI, null); DatabaseNotifyManager.create(requireContext()).notifyAllKeysChange();
} catch (IOException e) { } catch (IOException e) {
Timber.e(e, "IO Error"); Timber.e(e, "IO Error");
Notify.create(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR).show(); Notify.create(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR).show();

View File

@@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;

View File

@@ -49,7 +49,6 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;

View File

@@ -19,12 +19,6 @@ package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@@ -34,8 +28,6 @@ import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.Certification.CertDetails; import org.sufficientlysecure.keychain.model.Certification.CertDetails;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
public class CertListWidget extends ViewAnimator { public class CertListWidget extends ViewAnimator {
private TextView vCollapsed; private TextView vCollapsed;

View File

@@ -14,6 +14,9 @@ CREATE TABLE IF NOT EXISTS certs(
FOREIGN KEY(master_key_id, rank) REFERENCES user_packets(master_key_id, rank) ON DELETE CASCADE FOREIGN KEY(master_key_id, rank) REFERENCES user_packets(master_key_id, rank) ON DELETE CASCADE
); );
insertCert:
INSERT INTO certs VALUES (?, ?, ?, ?, ?, ?, ?);
selectVerifyingCertDetails: selectVerifyingCertDetails:
SELECT master_key_id AS masterKeyId, key_id_certifier AS signerMasterKeyId, creation * 1000 AS creation SELECT master_key_id AS masterKeyId, key_id_certifier AS signerMasterKeyId, creation * 1000 AS creation
FROM certs FROM certs

View File

@@ -3,6 +3,9 @@ CREATE TABLE IF NOT EXISTS keyrings_public (
key_ring_data BLOB NULL key_ring_data BLOB NULL
); );
insertKeyRingPublic:
INSERT INTO keyrings_public VALUES (?, ?);
selectAllMasterKeyIds: selectAllMasterKeyIds:
SELECT master_key_id SELECT master_key_id
FROM keyrings_public; FROM keyrings_public;

View File

@@ -5,6 +5,9 @@ CREATE TABLE IF NOT EXISTS key_signatures (
FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE
); );
insertKeySignature:
INSERT INTO key_signatures VALUES (?, ?);
selectMasterKeyIdsBySigner: selectMasterKeyIdsBySigner:
SELECT master_key_id SELECT master_key_id
FROM key_signatures WHERE signer_key_id IN ?; FROM key_signatures WHERE signer_key_id IN ?;

View File

@@ -23,6 +23,25 @@ CREATE TABLE IF NOT EXISTS keys (
keyrings_public(master_key_id) ON DELETE CASCADE keyrings_public(master_key_id) ON DELETE CASCADE
); );
insertKey:
INSERT INTO keys VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
updateHasSecretByMasterKeyId:
UPDATE keys
SET has_secret = ?2
WHERE master_key_id = ?1;
updateHasSecretByKeyId:
UPDATE keys
SET has_secret = ?2
WHERE key_id = ?1;
validKeysView:
CREATE VIEW validMasterKeys AS
SELECT *
FROM keys
WHERE rank = 0 AND is_revoked = 0 AND is_secure = 1 AND (expiry IS NULL OR expiry >= strftime('%s', 'now'));
unifiedKeyView: unifiedKeyView:
CREATE VIEW unifiedKeyView AS CREATE VIEW unifiedKeyView AS
SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.user_id, user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, keys.can_certify, certs.verified, SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.user_id, user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, keys.can_certify, certs.verified,
@@ -86,11 +105,11 @@ SELECT fingerprint
selectEffectiveSignKeyIdByMasterKeyId: selectEffectiveSignKeyIdByMasterKeyId:
SELECT key_id SELECT key_id
FROM keys FROM keys
WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= date('now') ) WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= strftime('%s', 'now') )
AND can_sign = 1 AND master_key_id = ?; AND can_sign = 1 AND master_key_id = ?;
selectEffectiveAuthKeyIdByMasterKeyId: selectEffectiveAuthKeyIdByMasterKeyId:
SELECT key_id SELECT key_id
FROM keys FROM keys
WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= date('now') ) WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= strftime('%s', 'now') )
AND can_authenticate = 1 AND master_key_id = ?; AND can_authenticate = 1 AND master_key_id = ?;

View File

@@ -16,6 +16,9 @@ CREATE TABLE IF NOT EXISTS user_packets(
keyrings_public(master_key_id) ON DELETE CASCADE keyrings_public(master_key_id) ON DELETE CASCADE
); );
insertUserPacket:
INSERT INTO user_packets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
selectUserIdsByMasterKeyId: selectUserIdsByMasterKeyId:
SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, MIN(certs.verified) AS verified_int SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, MIN(certs.verified) AS verified_int
FROM user_packets FROM user_packets
@@ -46,3 +49,23 @@ SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary
LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 )
WHERE user_packets.type = ? AND user_packets.master_key_id = ? AND user_packets.rank = ? WHERE user_packets.type = ? AND user_packets.master_key_id = ? AND user_packets.rank = ?
GROUP BY user_packets.master_key_id, user_packets.rank; GROUP BY user_packets.master_key_id, user_packets.rank;
uidStatus:
CREATE VIEW uidStatus AS
SELECT user_packets.email, MIN(certs.verified) AS key_status_int, user_packets.user_id, user_packets.master_key_id, COUNT(DISTINCT user_packets.master_key_id) AS candidates
FROM user_packets
JOIN validMasterKeys USING (master_key_id)
LEFT JOIN certs ON (certs.master_key_id = user_packets.master_key_id AND certs.rank = user_packets.rank AND certs.verified > 0)
WHERE user_packets.email IS NOT NULL
GROUP BY user_packets.email;
selectUserIdStatusByEmail:
SELECT *
FROM uidStatus
WHERE email IN ?;
selectUserIdStatusByEmailLike:
SELECT *
FROM uidStatus
WHERE email LIKE ?;

View File

@@ -18,19 +18,18 @@ import org.robolectric.shadows.ShadowBinder;
import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowPackageManager;
import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.KeychainTestRunner;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest; import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -49,10 +48,7 @@ public class KeychainExternalProviderTest {
static final String PACKAGE_NAME = "test.package"; static final String PACKAGE_NAME = "test.package";
static final byte[] PACKAGE_SIGNATURE = new byte[] { 1, 2, 3 }; static final byte[] PACKAGE_SIGNATURE = new byte[] { 1, 2, 3 };
static final String MAIL_ADDRESS_1 = "twi@openkeychain.org"; static final String MAIL_ADDRESS_1 = "twi@openkeychain.org";
static final String MAIL_ADDRESS_2 = "pink@openkeychain.org";
static final String MAIL_ADDRESS_SEC_1 = "twi-sec@openkeychain.org";
static final String USER_ID_1 = "twi <twi@openkeychain.org>"; static final String USER_ID_1 = "twi <twi@openkeychain.org>";
static final String USER_ID_SEC_1 = "twi <twi-sec@openkeychain.org>";
static final long KEY_ID_SECRET = 0x5D4DA4423C39122FL; static final long KEY_ID_SECRET = 0x5D4DA4423C39122FL;
static final long KEY_ID_PUBLIC = 0x9A282CE2AB44A382L; static final long KEY_ID_PUBLIC = 0x9A282CE2AB44A382L;
public static final String AUTOCRYPT_PEER = "tid"; public static final String AUTOCRYPT_PEER = "tid";
@@ -92,8 +88,8 @@ public class KeychainExternalProviderTest {
apiAppDao.deleteApiApp(PACKAGE_NAME); apiAppDao.deleteApiApp(PACKAGE_NAME);
contentResolver.query( contentResolver.query(
EmailStatus.CONTENT_URI, AutocryptStatus.CONTENT_URI,
new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID }, new String[] { AutocryptStatus.ADDRESS },
null, new String [] { }, null null, new String [] { }, null
); );
} }
@@ -104,106 +100,12 @@ public class KeychainExternalProviderTest {
apiAppDao.insertApiApp(ApiApp.create(PACKAGE_NAME, new byte[] { 1, 2, 4 })); apiAppDao.insertApiApp(ApiApp.create(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
contentResolver.query( contentResolver.query(
EmailStatus.CONTENT_URI, AutocryptStatus.CONTENT_URI,
new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID }, new String[] { AutocryptStatus.ADDRESS },
null, new String [] { }, null null, new String [] { }, null
); );
} }
@Test
public void testEmailStatus_withNonExistentAddress() throws Exception {
Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
null, new String [] { MAIL_ADDRESS_1 }, null
);
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(MAIL_ADDRESS_1, cursor.getString(0));
assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1));
assertTrue(cursor.isNull(2));
}
@Test
public void testEmailStatus() throws Exception {
insertPublicKeyringFrom("/test-keys/testring.pub");
Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
null, new String [] { MAIL_ADDRESS_1 }, null
);
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(MAIL_ADDRESS_1, cursor.getString(0));
assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(1));
assertEquals("twi <twi@openkeychain.org>", cursor.getString(2));
assertFalse(cursor.moveToNext());
}
@Test
public void testEmailStatus_multiple() throws Exception {
insertPublicKeyringFrom("/test-keys/testring.pub");
Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
null, new String [] { MAIL_ADDRESS_1, MAIL_ADDRESS_2 }, null
);
assertNotNull(cursor);
assertTrue(cursor.moveToNext());
assertEquals(MAIL_ADDRESS_2, cursor.getString(0));
assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1));
assertTrue(cursor.isNull(2));
assertTrue(cursor.moveToNext());
assertEquals(MAIL_ADDRESS_1, cursor.getString(0));
assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(1));
assertEquals("twi <twi@openkeychain.org>", cursor.getString(2));
assertFalse(cursor.moveToNext());
}
@Test
public void testEmailStatus_withSecretKey() throws Exception {
insertSecretKeyringFrom("/test-keys/testring.sec");
Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
null, new String [] { MAIL_ADDRESS_SEC_1 }, null
);
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(MAIL_ADDRESS_SEC_1, cursor.getString(0));
assertEquals(USER_ID_SEC_1, cursor.getString(2));
assertEquals(2, cursor.getInt(1));
assertFalse(cursor.moveToNext());
}
@Test
public void testEmailStatus_withConfirmedKey() throws Exception {
insertSecretKeyringFrom("/test-keys/testring.sec");
insertPublicKeyringFrom("/test-keys/testring.pub");
certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1);
Cursor cursor = contentResolver.query(
EmailStatus.CONTENT_URI, new String[] {
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID, EmailStatus.USER_ID_STATUS},
null, new String [] { MAIL_ADDRESS_1 }, null
);
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(MAIL_ADDRESS_1, cursor.getString(0));
assertEquals(USER_ID_1, cursor.getString(1));
assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(2));
assertFalse(cursor.moveToNext());
}
@Test @Test
public void testAutocryptStatus_autocryptPeer_withUnconfirmedKey() throws Exception { public void testAutocryptStatus_autocryptPeer_withUnconfirmedKey() throws Exception {
insertSecretKeyringFrom("/test-keys/testring.sec"); insertSecretKeyringFrom("/test-keys/testring.sec");