Migrate the identity table to be keyed off of libsignal IDs.

fork-5.53.8
Greyson Parrelli 2021-08-23 11:51:50 -04:00 zatwierdzone przez Alex Hart
rodzic 2068fa8041
commit dbb1e50d00
6 zmienionych plików z 236 dodań i 126 usunięć

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@ -11,6 +12,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.model.IdentityStoreRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.IdentityUtil;
@ -49,12 +51,12 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
public @NonNull SaveResult saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) {
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(address.getName());
RecipientId recipientId = RecipientId.fromExternalPush(address.getName());
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipientId);
if (!identityRecord.isPresent()) {
Log.i(TAG, "Saving new identity...");
identityDatabase.saveIdentity(recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
identityDatabase.saveIdentity(address.getName(), recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
return SaveResult.NEW;
}
@ -70,7 +72,8 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
verifiedStatus = VerifiedStatus.DEFAULT;
}
identityDatabase.saveIdentity(recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
identityDatabase.saveIdentity(address.getName(), recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
IdentityUtil.markIdentityUpdate(context, recipientId);
SessionUtil.archiveSiblingSessions(context, address);
DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(recipientId);
@ -95,65 +98,49 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
synchronized (LOCK) {
if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
RecipientId ourRecipientId = Recipient.self().getId();
RecipientId theirRecipientId = RecipientId.fromExternalPush(address.getName());
boolean isSelf = address.getName().equals(TextSecurePreferences.getLocalUuid(context).toString()) ||
address.getName().equals(TextSecurePreferences.getLocalNumber(context));
if (ourRecipientId.equals(theirRecipientId)) {
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
}
if (isSelf) {
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
}
switch (direction) {
case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirRecipientId));
case RECEIVING: return true;
default: throw new AssertionError("Unknown direction: " + direction);
}
} else {
Log.w(TAG, "Tried to check if identity is trusted for " + address.getName() + ", but no matching recipient existed!");
switch (direction) {
case SENDING: return false;
case RECEIVING: return true;
default: throw new AssertionError("Unknown direction: " + direction);
}
IdentityStoreRecord record = DatabaseFactory.getIdentityDatabase(context).getIdentityStoreRecord(address.getName());
switch (direction) {
case SENDING:
return isTrustedForSending(identityKey, record);
case RECEIVING:
return true;
default:
throw new AssertionError("Unknown direction: " + direction);
}
}
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) {
RecipientId recipientId = RecipientId.fromExternalPush(address.getName());
Optional<IdentityRecord> record = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipientId);
if (record.isPresent()) {
return record.get().getIdentityKey();
} else {
return null;
}
} else {
Log.w(TAG, "Tried to get identity for " + address.getName() + ", but no matching recipient existed!");
return null;
}
IdentityStoreRecord record = DatabaseFactory.getIdentityDatabase(context).getIdentityStoreRecord(address.getName());
return record != null ? record.getIdentityKey() : null;
}
private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) {
if (!identityRecord.isPresent()) {
private boolean isTrustedForSending(@NonNull IdentityKey identityKey, @Nullable IdentityStoreRecord identityRecord) {
if (identityRecord == null) {
Log.w(TAG, "Nothing here, returning true...");
return true;
}
if (!identityKey.equals(identityRecord.get().getIdentityKey())) {
Log.w(TAG, "Identity keys don't match... service: " + identityKey.hashCode() + " database: " + identityRecord.get().getIdentityKey().hashCode());
if (!identityKey.equals(identityRecord.getIdentityKey())) {
Log.w(TAG, "Identity keys don't match... service: " + identityKey.hashCode() + " database: " + identityRecord.getIdentityKey().hashCode());
return false;
}
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
Log.w(TAG, "Needs unverified approval!");
return false;
}
if (isNonBlockingApprovalRequired(identityRecord.get())) {
if (isNonBlockingApprovalRequired(identityRecord)) {
Log.w(TAG, "Needs non-blocking approval!");
return false;
}
@ -162,9 +149,17 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
}
private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) {
return !identityRecord.isFirstUse() &&
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) &&
!identityRecord.isApprovedNonBlocking();
return isNonBlockingApprovalRequired(identityRecord.isFirstUse(), identityRecord.getTimestamp(), identityRecord.isApprovedNonBlocking());
}
private boolean isNonBlockingApprovalRequired(IdentityStoreRecord identityRecord) {
return isNonBlockingApprovalRequired(identityRecord.getFirstUse(), identityRecord.getTimestamp(), identityRecord.getNonblockingApproval());
}
private boolean isNonBlockingApprovalRequired(boolean firstUse, long timestamp, boolean nonblockingApproval) {
return !firstUse &&
!nonblockingApproval &&
System.currentTimeMillis() - timestamp < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS);
}
public enum SaveResult {

Wyświetl plik

@ -27,10 +27,13 @@ import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.database.model.IdentityStoreRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.guava.Optional;
@ -38,6 +41,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
public class IdentityDatabase extends Database {
@ -46,37 +50,40 @@ public class IdentityDatabase extends Database {
static final String TABLE_NAME = "identities";
private static final String ID = "_id";
static final String RECIPIENT_ID = "address";
static final String IDENTITY_KEY = "key";
private static final String TIMESTAMP = "timestamp";
static final String ADDRESS = "address";
static final String IDENTITY_KEY = "identity_key";
private static final String FIRST_USE = "first_use";
private static final String NONBLOCKING_APPROVAL = "nonblocking_approval";
private static final String TIMESTAMP = "timestamp";
static final String VERIFIED = "verified";
private static final String NONBLOCKING_APPROVAL = "nonblocking_approval";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " +
RECIPIENT_ID + " INTEGER UNIQUE, " +
IDENTITY_KEY + " TEXT, " +
FIRST_USE + " INTEGER DEFAULT 0, " +
TIMESTAMP + " INTEGER DEFAULT 0, " +
VERIFIED + " INTEGER DEFAULT 0, " +
NONBLOCKING_APPROVAL + " INTEGER DEFAULT 0);";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
ADDRESS + " INTEGER UNIQUE, " +
IDENTITY_KEY + " TEXT, " +
FIRST_USE + " INTEGER DEFAULT 0, " +
TIMESTAMP + " INTEGER DEFAULT 0, " +
VERIFIED + " INTEGER DEFAULT 0, " +
NONBLOCKING_APPROVAL + " INTEGER DEFAULT 0);";
public enum VerifiedStatus {
DEFAULT, VERIFIED, UNVERIFIED;
public int toInt() {
if (this == DEFAULT) return 0;
else if (this == VERIFIED) return 1;
else if (this == UNVERIFIED) return 2;
else throw new AssertionError();
switch (this) {
case DEFAULT: return 0;
case VERIFIED: return 1;
case UNVERIFIED: return 2;
default: throw new AssertionError();
}
}
public static VerifiedStatus forState(int state) {
if (state == 0) return DEFAULT;
else if (state == 1) return VERIFIED;
else if (state == 2) return UNVERIFIED;
else throw new AssertionError("No such state: " + state);
switch (state) {
case 0: return DEFAULT;
case 1: return VERIFIED;
case 2: return UNVERIFIED;
default: throw new AssertionError("No such state: " + state);
}
}
}
@ -94,55 +101,108 @@ public class IdentityDatabase extends Database {
return new IdentityReader(cursor);
}
public Optional<IdentityRecord> getIdentity(@NonNull RecipientId recipientId) {
public Optional<IdentityRecord> getIdentity(@NonNull String addressName) {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
Cursor cursor = null;
String query = ADDRESS + " = ?";
String[] args = SqlUtil.buildArgs(addressName);
try {
cursor = database.query(TABLE_NAME, null, RECIPIENT_ID + " = ?",
new String[] {recipientId.serialize()}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
if (cursor.moveToFirst()) {
return Optional.of(getIdentityRecord(cursor));
}
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
} finally {
if (cursor != null) cursor.close();
}
return Optional.absent();
}
public Optional<IdentityRecord> getIdentity(@NonNull RecipientId recipientId) {
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.hasServiceIdentifier()) {
return getIdentity(recipient.requireServiceId());
} else {
Log.w(TAG, "Recipient has no service identifier!");
return Optional.absent();
}
}
public @Nullable IdentityStoreRecord getIdentityStoreRecord(@NonNull String addressName) {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
String query = ADDRESS + " = ?";
String[] args = SqlUtil.buildArgs(addressName);
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
if (cursor.moveToFirst()) {
String serializedIdentity = CursorUtil.requireString(cursor, IDENTITY_KEY);
long timestamp = CursorUtil.requireLong(cursor, TIMESTAMP);
int verifiedStatus = CursorUtil.requireInt(cursor, VERIFIED);
boolean nonblockingApproval = CursorUtil.requireBoolean(cursor, NONBLOCKING_APPROVAL);
boolean firstUse = CursorUtil.requireBoolean(cursor, FIRST_USE);
return new IdentityStoreRecord(addressName,
new IdentityKey(Base64.decode(serializedIdentity), 0),
VerifiedStatus.forState(verifiedStatus),
firstUse,
timestamp,
nonblockingApproval);
}
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
}
return null;
}
public @NonNull IdentityRecordList getIdentities(@NonNull List<Recipient> recipients) {
List<IdentityRecord> records = new LinkedList<>();
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
String[] selectionArgs = new String[1];
List<String> addressNames = recipients.stream()
.filter(Recipient::hasServiceIdentifier)
.map(Recipient::requireServiceId)
.collect(Collectors.toList());
database.beginTransaction();
try {
for (Recipient recipient : recipients) {
selectionArgs[0] = recipient.getId().serialize();
if (addressNames.isEmpty()) {
return IdentityRecordList.EMPTY;
}
try (Cursor cursor = database.query(TABLE_NAME, null, RECIPIENT_ID + " = ?", selectionArgs, null, null, null)) {
if (cursor.moveToFirst()) {
records.add(getIdentityRecord(cursor));
}
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ADDRESS, addressNames);
List<IdentityRecord> records = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, null, query.getWhere(), query.getWhereArgs(), null, null, null)) {
while (cursor.moveToNext()) {
try {
records.add(getIdentityRecord(cursor));
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
}
}
} finally {
database.endTransaction();
}
return new IdentityRecordList(records);
}
public void saveIdentity(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus,
boolean firstUse, long timestamp, boolean nonBlockingApproval)
public void saveIdentity(@NonNull String addressName,
@NonNull RecipientId recipientId,
IdentityKey identityKey,
VerifiedStatus verifiedStatus,
boolean firstUse,
long timestamp,
boolean nonBlockingApproval)
{
saveIdentityInternal(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
saveIdentityInternal(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
}
public void saveIdentity(@NonNull RecipientId recipientId,
IdentityKey identityKey,
VerifiedStatus verifiedStatus,
boolean firstUse,
long timestamp,
boolean nonBlockingApproval)
{
saveIdentityInternal(Recipient.resolved(recipientId).requireServiceId(), identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
}
@ -152,7 +212,7 @@ public class IdentityDatabase extends Database {
ContentValues contentValues = new ContentValues(2);
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval);
database.update(TABLE_NAME, contentValues, RECIPIENT_ID + " = ?", new String[] {recipientId.serialize()});
database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", SqlUtil.buildArgs(Recipient.resolved(recipientId).requireServiceId()));
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
}
@ -160,11 +220,13 @@ public class IdentityDatabase extends Database {
public void setVerified(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
String[] args = SqlUtil.buildArgs(Recipient.resolved(recipientId).requireServiceId(), Base64.encodeBytes(identityKey.serialize()));
ContentValues contentValues = new ContentValues(1);
contentValues.put(VERIFIED, verifiedStatus.toInt());
int updated = database.update(TABLE_NAME, contentValues, RECIPIENT_ID + " = ? AND " + IDENTITY_KEY + " = ?",
new String[] {recipientId.serialize(), Base64.encodeBytes(identityKey.serialize())});
int updated = database.update(TABLE_NAME, contentValues, query, args);
if (updated > 0) {
Optional<IdentityRecord> record = getIdentity(recipientId);
@ -173,36 +235,40 @@ public class IdentityDatabase extends Database {
}
}
public void updateIdentityAfterSync(@NonNull RecipientId id, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
boolean hadEntry = getIdentity(id).isPresent();
boolean keyMatches = hasMatchingKey(id, identityKey);
boolean statusMatches = keyMatches && hasMatchingStatus(id, identityKey, verifiedStatus);
public void updateIdentityAfterSync(@NonNull String addressName, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
boolean hadEntry = getIdentity(addressName).isPresent();
boolean keyMatches = hasMatchingKey(addressName, identityKey);
boolean statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus);
if (!keyMatches || !statusMatches) {
saveIdentityInternal(id, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
Optional<IdentityRecord> record = getIdentity(id);
if (record.isPresent()) EventBus.getDefault().post(record.get());
saveIdentityInternal(addressName, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
Optional<IdentityRecord> record = getIdentity(addressName);
if (record.isPresent()) {
EventBus.getDefault().post(record.get());
}
}
if (hadEntry && !keyMatches) {
IdentityUtil.markIdentityUpdate(context, id);
IdentityUtil.markIdentityUpdate(context, RecipientId.fromExternalPush(addressName));
}
}
private boolean hasMatchingKey(@NonNull RecipientId id, IdentityKey identityKey) {
private boolean hasMatchingKey(@NonNull String addressName, IdentityKey identityKey) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String query = RECIPIENT_ID + " = ? AND " + IDENTITY_KEY + " = ?";
String[] args = new String[]{id.serialize(), Base64.encodeBytes(identityKey.serialize())};
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()));
try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
return cursor != null && cursor.moveToFirst();
}
}
private boolean hasMatchingStatus(@NonNull RecipientId id, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
private boolean hasMatchingStatus(@NonNull String addressName, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String query = RECIPIENT_ID + " = ? AND " + IDENTITY_KEY + " = ? AND " + VERIFIED + " = ?";
String[] args = new String[]{id.serialize(), Base64.encodeBytes(identityKey.serialize()), String.valueOf(verifiedStatus.toInt())};
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ? AND " + VERIFIED + " = ?";
String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()), verifiedStatus.toInt());
try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
return cursor != null && cursor.moveToFirst();
@ -210,25 +276,29 @@ public class IdentityDatabase extends Database {
}
private static @NonNull IdentityRecord getIdentityRecord(@NonNull Cursor cursor) throws IOException, InvalidKeyException {
long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID));
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
int verifiedStatus = cursor.getInt(cursor.getColumnIndexOrThrow(VERIFIED));
boolean nonblockingApproval = cursor.getInt(cursor.getColumnIndexOrThrow(NONBLOCKING_APPROVAL)) == 1;
boolean firstUse = cursor.getInt(cursor.getColumnIndexOrThrow(FIRST_USE)) == 1;
String addressName = CursorUtil.requireString(cursor, ADDRESS);
String serializedIdentity = CursorUtil.requireString(cursor, IDENTITY_KEY);
long timestamp = CursorUtil.requireLong(cursor, TIMESTAMP);
int verifiedStatus = CursorUtil.requireInt(cursor, VERIFIED);
boolean nonblockingApproval = CursorUtil.requireBoolean(cursor, NONBLOCKING_APPROVAL);
boolean firstUse = CursorUtil.requireBoolean(cursor, FIRST_USE);
IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0);
return new IdentityRecord(RecipientId.from(recipientId), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval);
return new IdentityRecord(RecipientId.fromExternalPush(addressName), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval);
}
private void saveIdentityInternal(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus,
boolean firstUse, long timestamp, boolean nonBlockingApproval)
private void saveIdentityInternal(@NonNull String addressName,
IdentityKey identityKey,
VerifiedStatus verifiedStatus,
boolean firstUse,
long timestamp,
boolean nonBlockingApproval)
{
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
ContentValues contentValues = new ContentValues();
contentValues.put(RECIPIENT_ID, recipientId.serialize());
contentValues.put(ADDRESS, addressName);
contentValues.put(IDENTITY_KEY, identityKeyString);
contentValues.put(TIMESTAMP, timestamp);
contentValues.put(VERIFIED, verifiedStatus.toInt());
@ -237,8 +307,7 @@ public class IdentityDatabase extends Database {
database.replace(TABLE_NAME, null, contentValues);
EventBus.getDefault().post(new IdentityRecord(recipientId, identityKey, verifiedStatus,
firstUse, timestamp, nonBlockingApproval));
EventBus.getDefault().post(new IdentityRecord(RecipientId.fromExternalPush(addressName), identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
}
public static class IdentityRecord {
@ -251,8 +320,11 @@ public class IdentityDatabase extends Database {
private final boolean nonblockingApproval;
private IdentityRecord(@NonNull RecipientId recipientId,
IdentityKey identitykey, VerifiedStatus verifiedStatus,
boolean firstUse, long timestamp, boolean nonblockingApproval)
IdentityKey identitykey,
VerifiedStatus verifiedStatus,
boolean firstUse,
long timestamp,
boolean nonblockingApproval)
{
this.recipientId = recipientId;
this.identitykey = identitykey;
@ -293,7 +365,7 @@ public class IdentityDatabase extends Database {
}
public class IdentityReader {
public static class IdentityReader {
private final Cursor cursor;
IdentityReader(@NonNull Cursor cursor) {

Wyświetl plik

@ -802,7 +802,7 @@ public class RecipientDatabase extends Database {
try {
IdentityKey identityKey = new IdentityKey(insert.getIdentityKey().get(), 0);
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.getIdentityState()));
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(insert.getAddress().getIdentifier(), identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.getIdentityState()));
} catch (InvalidKeyException e) {
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e);
}
@ -846,7 +846,7 @@ public class RecipientDatabase extends Database {
if (update.getNew().getIdentityKey().isPresent()) {
IdentityKey identityKey = new IdentityKey(update.getNew().getIdentityKey().get(), 0);
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.getNew().getIdentityState()));
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(update.getNew().getAddress().getIdentifier(), identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.getNew().getIdentityState()));
}
Optional<IdentityRecord> newIdentityRecord = identityDatabase.getIdentity(recipientId);
@ -1093,7 +1093,7 @@ public class RecipientDatabase extends Database {
private List<RecipientSettings> getRecipientSettingsForSync(@Nullable String query, @Nullable String[] args) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + UUID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.ADDRESS
+ " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID
+ " LEFT OUTER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID;
List<RecipientSettings> out = new ArrayList<>();
@ -2913,7 +2913,7 @@ public class RecipientDatabase extends Database {
db.update(TABLE_NAME, uuidValues, ID_WHERE, SqlUtil.buildArgs(byUuid));
// Identities
db.delete(IdentityDatabase.TABLE_NAME, IdentityDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(byE164));
db.delete(IdentityDatabase.TABLE_NAME, IdentityDatabase.ADDRESS + " = ?", SqlUtil.buildArgs(byE164));
// Group Receipts
ContentValues groupReceiptValues = new ContentValues();

Wyświetl plik

@ -211,8 +211,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int AVATAR_PICKER = 111;
private static final int THREAD_CLEANUP = 112;
private static final int SESSION_MIGRATION = 113;
private static final int IDENTITY_MIGRATION = 114;
private static final int DATABASE_VERSION = 113;
private static final int DATABASE_VERSION = 114;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -1988,6 +1989,33 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
Log.d(TAG, "Session migration took " + (System.currentTimeMillis() - start) + " ms");
}
if (oldVersion < IDENTITY_MIGRATION) {
long start = System.currentTimeMillis();
db.execSQL("CREATE TABLE identities_tmp (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"address TEXT UNIQUE NOT NULL, " +
"identity_key TEXT, " +
"first_use INTEGER DEFAULT 0, " +
"timestamp INTEGER DEFAULT 0, " +
"verified INTEGER DEFAULT 0, " +
"nonblocking_approval INTEGER DEFAULT 0)");
db.execSQL("INSERT INTO identities_tmp (address, identity_key, first_use, timestamp, verified, nonblocking_approval) " +
"SELECT COALESCE(recipient.uuid, recipient.phone) AS new_address, " +
"identities.key, " +
"identities.first_use, " +
"identities.timestamp, " +
"identities.verified, " +
"identities.nonblocking_approval " +
"FROM identities INNER JOIN recipient ON identities.address = recipient._id " +
"WHERE new_address NOT NULL");
db.execSQL("DROP TABLE identities");
db.execSQL("ALTER TABLE identities_tmp RENAME TO identities");
Log.d(TAG, "Identity migration took " + (System.currentTimeMillis() - start) + " ms");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

Wyświetl plik

@ -14,6 +14,8 @@ import java.util.concurrent.TimeUnit;
public final class IdentityRecordList {
public static final IdentityRecordList EMPTY = new IdentityRecordList(Collections.emptyList());
private final List<IdentityRecord> identityRecords;
private final boolean isVerified;
private final boolean isUnverified;

Wyświetl plik

@ -0,0 +1,13 @@
package org.thoughtcrime.securesms.database.model
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.whispersystems.libsignal.IdentityKey
data class IdentityStoreRecord(
val addressName: String,
val identityKey: IdentityKey,
val verifiedStatus: IdentityDatabase.VerifiedStatus,
val firstUse: Boolean,
val timestamp: Long,
val nonblockingApproval: Boolean
)