extract select by signer from KeychainProvider

This commit is contained in:
Vincent Breitmoser
2018-06-22 19:58:17 +02:00
parent 6cd065a3bd
commit d57a409fac
13 changed files with 87 additions and 128 deletions

View File

@@ -1,41 +0,0 @@
package org.sufficientlysecure.keychain.livedata;
import java.util.ArrayList;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.Context;
import android.database.Cursor;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
public class KeyRingDao {
private final SupportSQLiteDatabase db;
public static KeyRingDao getInstance(Context context) {
KeychainDatabase keychainDatabase = new KeychainDatabase(context);
return new KeyRingDao(keychainDatabase.getWritableDatabase());
}
private KeyRingDao(SupportSQLiteDatabase writableDatabase) {
this.db = writableDatabase;
}
public List<UnifiedKeyInfo> getUnifiedKeyInfo() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
List<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = db.query(query)) {
while (cursor.moveToNext()) {
UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(unifiedKeyInfo);
}
}
return result;
}
}

View File

@@ -30,7 +30,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
@@ -113,15 +112,14 @@ public abstract class BaseOperation<T extends Parcelable> implements PassphraseC
@Override @Override
public Passphrase getCachedPassphrase(long subKeyId) throws NoSecretKeyException { public Passphrase getCachedPassphrase(long subKeyId) throws NoSecretKeyException {
try { if (subKeyId != key.symmetric) {
if (subKeyId != key.symmetric) { Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(subKeyId);
long masterKeyId = mKeyRepository.getMasterKeyId(subKeyId); if (masterKeyId == null) {
return getCachedPassphrase(masterKeyId, subKeyId); throw new PassphraseCacheInterface.NoSecretKeyException();
} }
return getCachedPassphrase(key.symmetric, key.symmetric); return getCachedPassphrase(masterKeyId, subKeyId);
} catch (NotFoundException e) {
throw new PassphraseCacheInterface.NoSecretKeyException();
} }
return getCachedPassphrase(key.symmetric, key.symmetric);
} }
@Override @Override

View File

@@ -33,6 +33,7 @@ import com.squareup.sqldelight.SqlDelightQuery;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.model.KeyRingPublic;
import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.model.UserPacket;
import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -172,7 +173,7 @@ public class KeyRepository extends AbstractDao {
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
} }
public long getMasterKeyId(long subKeyId) throws NotFoundException { public long getMasterKeyIdBySubKeyId(long subKeyId) throws NotFoundException {
return (Long) getGenericData(KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId), return (Long) getGenericData(KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId),
KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER); KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
} }
@@ -238,6 +239,33 @@ public class KeyRepository extends AbstractDao {
} }
} }
public List<Long> getAllMasterKeyIds() {
SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds();
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map);
}
public List<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) {
long[] signerKeyIds = new long[signerMasterKeyIds.size()];
int i = 0;
for (Long signerKeyId : signerMasterKeyIds) {
signerKeyIds[i++] = signerKeyId;
}
SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds);
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map);
}
public List<UnifiedKeyInfo> getUnifiedKeyInfo() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
List<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(unifiedKeyInfo);
}
}
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); return mapAllRows(query, UserPacket.USER_ID_MAPPER::map);

View File

@@ -1013,30 +1013,18 @@ public class KeyWritableRepository extends KeyRepository {
log.add(LogType.MSG_TRUST, 0); log.add(LogType.MSG_TRUST, 0);
Cursor cursor;
Preferences preferences = Preferences.getPreferences(context); Preferences preferences = Preferences.getPreferences(context);
boolean isTrustDbInitialized = preferences.isKeySignaturesTableInitialized(); boolean isTrustDbInitialized = preferences.isKeySignaturesTableInitialized();
List<Long> masterKeyIds;
if (!isTrustDbInitialized) { if (!isTrustDbInitialized) {
log.add(LogType.MSG_TRUST_INITIALIZE, 1); log.add(LogType.MSG_TRUST_INITIALIZE, 1);
cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), masterKeyIds = getAllMasterKeyIds();
new String[] { KeyRings.MASTER_KEY_ID }, null, null, null);
} else { } else {
String[] signerMasterKeyIdStrings = new String[signerMasterKeyIds.size()]; masterKeyIds = getMasterKeyIdsBySigner(signerMasterKeyIds);
int i = 0;
for (Long masterKeyId : signerMasterKeyIds) {
log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
signerMasterKeyIdStrings[i++] = Long.toString(masterKeyId);
}
cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsFilterBySigner(),
new String[] { KeyRings.MASTER_KEY_ID }, null, signerMasterKeyIdStrings, null);
} }
if (cursor == null) { int totalKeys = masterKeyIds.size();
throw new IllegalStateException();
}
int totalKeys = cursor.getCount();
int processedKeys = 0; int processedKeys = 0;
if (totalKeys == 0) { if (totalKeys == 0) {
@@ -1046,34 +1034,30 @@ public class KeyWritableRepository extends KeyRepository {
log.add(LogType.MSG_TRUST_COUNT, 1, totalKeys); log.add(LogType.MSG_TRUST_COUNT, 1, totalKeys);
} }
try { for (long masterKeyId : masterKeyIds) {
while (cursor.moveToNext()) { try {
try { log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
long masterKeyId = cursor.getLong(0);
byte[] pubKeyData = loadPublicKeyRingData(masterKeyId); byte[] pubKeyData = loadPublicKeyRingData(masterKeyId);
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData); UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData);
clearLog(); clearLog();
SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true); SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true);
log.add(result, 1); log.add(result, 1);
progress.setProgress(processedKeys++, totalKeys); progress.setProgress(processedKeys++, totalKeys);
} catch (NotFoundException | PgpGeneralException | IOException e) { } catch (NotFoundException | PgpGeneralException | IOException e) {
Timber.e(e, "Error updating trust database"); Timber.e(e, "Error updating trust database");
return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log); return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log);
}
} }
if (!isTrustDbInitialized) {
preferences.setKeySignaturesTableInitialized();
}
log.add(LogType.MSG_TRUST_OK, 1);
return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log);
} finally {
cursor.close();
} }
if (!isTrustDbInitialized) {
preferences.setKeySignaturesTableInitialized();
}
log.add(LogType.MSG_TRUST_OK, 1);
return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log);
} }
/** /**

View File

@@ -104,9 +104,6 @@ public class KeychainContract {
public static final String PATH_BY_SUBKEY = "subkey"; public static final String PATH_BY_SUBKEY = "subkey";
public static final String PATH_BY_USER_ID = "user_id"; public static final String PATH_BY_USER_ID = "user_id";
public static final String PATH_FILTER = "filter";
public static final String PATH_BY_SIGNER = "signer";
public static final String PATH_PUBLIC = "public"; public static final String PATH_PUBLIC = "public";
public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys"; public static final String PATH_KEYS = "keys";
@@ -170,10 +167,6 @@ public class KeychainContract {
return CONTENT_URI.buildUpon().appendPath(PATH_FIND) return CONTENT_URI.buildUpon().appendPath(PATH_FIND)
.appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build(); .appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build();
} }
public static Uri buildUnifiedKeyRingsFilterBySigner() {
return CONTENT_URI.buildUpon().appendPath(PATH_FILTER).appendPath(PATH_BY_SIGNER).build();
}
} }
public static class KeyRingData implements KeyRingsColumns, BaseColumns { public static class KeyRingData implements KeyRingsColumns, BaseColumns {

View File

@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.CertsModel;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeyMetadataModel; import org.sufficientlysecure.keychain.KeyMetadataModel;
import org.sufficientlysecure.keychain.KeyRingsPublicModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel;
import org.sufficientlysecure.keychain.KeySignaturesModel;
import org.sufficientlysecure.keychain.UserPacketsModel; import org.sufficientlysecure.keychain.UserPacketsModel;
import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.model.Certification; import org.sufficientlysecure.keychain.model.Certification;
@@ -199,7 +200,7 @@ public class KeychainDatabase {
db.execSQL(UserPacketsModel.CREATE_TABLE); db.execSQL(UserPacketsModel.CREATE_TABLE);
db.execSQL(CertsModel.CREATE_TABLE); db.execSQL(CertsModel.CREATE_TABLE);
db.execSQL(KeyMetadataModel.CREATE_TABLE); db.execSQL(KeyMetadataModel.CREATE_TABLE);
db.execSQL(CREATE_KEY_SIGNATURES); db.execSQL(KeySignaturesModel.CREATE_TABLE);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
db.execSQL(AutocryptPeersModel.CREATE_TABLE); db.execSQL(AutocryptPeersModel.CREATE_TABLE);

View File

@@ -65,7 +65,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
private static final int KEY_RINGS_FIND_BY_USER_ID = 402; private static final int KEY_RINGS_FIND_BY_USER_ID = 402;
private static final int KEY_RINGS_FILTER_BY_SIGNER = 403;
private static final int KEY_SIGNATURES = 700; private static final int KEY_SIGNATURES = 700;
@@ -108,9 +107,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_USER_ID + "/*", + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_USER_ID + "/*",
KEY_RINGS_FIND_BY_USER_ID); KEY_RINGS_FIND_BY_USER_ID);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_FILTER + "/" + KeychainContract.PATH_BY_SIGNER,
KEY_RINGS_FILTER_BY_SIGNER);
/* /*
* list key_ring specifics * list key_ring specifics
@@ -191,8 +187,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
case KEY_RINGS_UNIFIED: case KEY_RINGS_UNIFIED:
case KEY_RINGS_FIND_BY_EMAIL: case KEY_RINGS_FIND_BY_EMAIL:
case KEY_RINGS_FIND_BY_SUBKEY: case KEY_RINGS_FIND_BY_SUBKEY:
case KEY_RINGS_FIND_BY_USER_ID: case KEY_RINGS_FIND_BY_USER_ID: {
case KEY_RINGS_FILTER_BY_SIGNER: {
HashMap<String, String> projectionMap = new HashMap<>(); HashMap<String, String> projectionMap = new HashMap<>();
projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id");
projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
@@ -356,23 +351,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
} }
break; break;
} }
case KEY_RINGS_FILTER_BY_SIGNER: {
StringBuilder signerKeyIds = new StringBuilder();
signerKeyIds.append(selectionArgs[0]);
for (int i = 1; i < selectionArgs.length; i++) {
signerKeyIds.append(',').append(selectionArgs[i]);
}
qb.appendWhere(" AND EXISTS (SELECT 1 FROM " + Tables.KEY_SIGNATURES + " WHERE " +
Tables.KEY_SIGNATURES + "." + KeySignatures.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID +
" AND " +
Tables.KEY_SIGNATURES + "." + KeySignatures.SIGNER_KEY_ID + " IN (" + signerKeyIds + ")" +
")");
selection = null;
selectionArgs = null;
break;
}
case KEY_RINGS_FIND_BY_EMAIL: case KEY_RINGS_FIND_BY_EMAIL:
case KEY_RINGS_FIND_BY_USER_ID: { case KEY_RINGS_FIND_BY_USER_ID: {
String chunks[] = uri.getLastPathSegment().split(" *, *"); String chunks[] = uri.getLastPathSegment().split(" *, *");

View File

@@ -196,7 +196,8 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
try { try {
Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class); Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class);
long masterKeyId = KeyRepository.create(getContext()).getCachedPublicKeyRing( KeyRepository keyRepository = KeyRepository.create(requireContext());
long masterKeyId = keyRepository.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId) KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId)
).getMasterKeyId(); ).getMasterKeyId();
viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));

View File

@@ -86,7 +86,8 @@ public class DeleteKeyDialogActivity extends FragmentActivity {
if (mMasterKeyIds.length == 1 && mHasSecret) { if (mMasterKeyIds.length == 1 && mHasSecret) {
// if mMasterKeyIds.length == 0 we let the DeleteOperation respond // if mMasterKeyIds.length == 0 we let the DeleteOperation respond
try { try {
HashMap<String, Object> data = KeyRepository.create(this).getUnifiedData( KeyRepository keyRepository = KeyRepository.create(this);
HashMap<String, Object> data = keyRepository.getUnifiedData(
mMasterKeyIds[0], new String[]{ mMasterKeyIds[0], new String[]{
KeychainContract.KeyRings.NAME, KeychainContract.KeyRings.NAME,
KeychainContract.KeyRings.IS_REVOKED KeychainContract.KeyRings.IS_REVOKED

View File

@@ -58,11 +58,11 @@ import org.sufficientlysecure.keychain.Constants;
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.keysync.KeyserverSyncManager; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.livedata.KeyRingDao;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
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.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
@@ -256,18 +256,18 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
} }
public static class KeyListLiveData extends AsyncTaskLiveData<List<FlexibleKeyItem>> { public static class KeyListLiveData extends AsyncTaskLiveData<List<FlexibleKeyItem>> {
private final KeyRingDao keyRingDao; private final KeyRepository keyRepository;
private FlexibleKeyItemFactory flexibleKeyItemFactory; private FlexibleKeyItemFactory flexibleKeyItemFactory;
KeyListLiveData(@NonNull Context context) { KeyListLiveData(@NonNull Context context) {
super(context, KeyRings.CONTENT_URI); super(context, KeyRings.CONTENT_URI);
keyRingDao = KeyRingDao.getInstance(context.getApplicationContext()); keyRepository = KeyRepository.create(context.getApplicationContext());
flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources()); flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources());
} }
@Override @Override
protected List<FlexibleKeyItem> asyncLoadData() { protected List<FlexibleKeyItem> asyncLoadData() {
List<UnifiedKeyInfo> unifiedKeyInfo = keyRingDao.getUnifiedKeyInfo(); List<UnifiedKeyInfo> unifiedKeyInfo = keyRepository.getUnifiedKeyInfo();
return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo); return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
} }
} }

View File

@@ -3,6 +3,10 @@ CREATE TABLE IF NOT EXISTS keyrings_public (
key_ring_data BLOB NULL key_ring_data BLOB NULL
); );
selectAllMasterKeyIds:
SELECT master_key_id
FROM keyrings_public;
selectByMasterKeyId: selectByMasterKeyId:
SELECT * SELECT *
FROM keyrings_public FROM keyrings_public

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS key_signatures (
master_key_id INTEGER NOT NULL,
signer_key_id INTEGER NOT NULL,
PRIMARY KEY(master_key_id, signer_key_id),
FOREIGN KEY(master_key_id) REFERENCES
keyrings_public(master_key_id) ON DELETE CASCADE
);

View File

@@ -47,3 +47,8 @@ selectSecretKeyType:
SELECT has_secret SELECT has_secret
FROM keys FROM keys
WHERE key_id = ?; WHERE key_id = ?;
-- TODO move to KeySignatures.sq
selectMasterKeyIdsBySigner:
SELECT master_key_id
FROM key_signatures WHERE signer_key_id IN ?;