Merge pull request #93 from loki-project/session-reset-fix

Session reset fixes.
This commit is contained in:
gmbnt 2020-02-14 14:46:00 +11:00 committed by GitHub
commit c171a906a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 35 deletions

View File

@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.logging.Log;
import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.signalservice.loki.messaging.LokiSessionDatabaseProtocol; import org.whispersystems.libsignal.state.SessionStore;
import java.util.List; import java.util.List;
public class TextSecureSessionStore implements LokiSessionDatabaseProtocol { public class TextSecureSessionStore implements SessionStore {
private static final String TAG = TextSecureSessionStore.class.getSimpleName(); private static final String TAG = TextSecureSessionStore.class.getSimpleName();

View File

@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.CreateProfileActivity; import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment; import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent; import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.gcm.FcmService; import org.thoughtcrime.securesms.gcm.FcmService;
@ -48,6 +47,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob; import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener; import org.thoughtcrime.securesms.push.MessageSenderEventListener;
@ -160,7 +160,7 @@ public class SignalCommunicationModule {
DatabaseFactory.getLokiThreadDatabase(context), DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context), DatabaseFactory.getLokiMessageDatabase(context),
DatabaseFactory.getLokiPreKeyBundleDatabase(context), DatabaseFactory.getLokiPreKeyBundleDatabase(context),
new TextSecureSessionStore(context), new LokiSessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context), DatabaseFactory.getLokiUserDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster); ((ApplicationContext)context.getApplicationContext()).broadcaster);
} else { } else {

View File

@ -69,12 +69,12 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.FriendRequestHandler;
import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.LokiSessionResetImplementation;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity; import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities; import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase; import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyBundleDatabase;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPreKeyRecordDatabase;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
@ -101,6 +101,8 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol;
import org.whispersystems.libsignal.loki.LokiSessionResetStatus;
import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -137,7 +139,6 @@ import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage; import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.InputStream; import java.io.InputStream;
@ -270,9 +271,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
LokiPreKeyRecordDatabase lokiPreKeyRecordDatabase = DatabaseFactory.getLokiPreKeyRecordDatabase(context); LokiSessionResetProtocol lokiSessionResetProtocol = new LokiSessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context)); SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiThreadDatabase, lokiPreKeyRecordDatabase, UnidentifiedAccessUtil.getCertificateValidator()); LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiSessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
SignalServiceContent content = cipher.decrypt(envelope); SignalServiceContent content = cipher.decrypt(envelope);
@ -363,6 +364,18 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
handleNeedsDeliveryReceipt(content, message); handleNeedsDeliveryReceipt(content, message);
} }
// If we received a friend request, but we were already friends with the user, reset the session
if (content.isFriendRequest() && !message.isGroupMessage()) {
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
resetSession(content.getSender());
// Let our other devices know that we have reset the session
MessageSender.syncContact(context, sender.getAddress());
}
}
// Loki - Handle friend request logic if needed // Loki - Handle friend request logic if needed
updateFriendRequestStatusIfNeeded(content, message); updateFriendRequestStatusIfNeeded(content, message);
} }
@ -403,11 +416,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (envelope.isPreKeySignalMessage()) { if (envelope.isPreKeySignalMessage()) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob()); ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob());
} }
// Loki - Handle session reset logic
if (!content.isFriendRequest()) {
cipher.handleSessionResetRequestIfNeeded(content, cipher.getSessionStatus(content));
}
} catch (ProtocolInvalidVersionException e) { } catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId); handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
@ -540,19 +548,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
if (threadId != null) { if (threadId != null) {
resetSession(content.getSender(), threadId); resetSession(content.getSender());
MessageNotifier.updateNotification(context, threadId); MessageNotifier.updateNotification(context, threadId);
} }
} }
private void resetSession(String hexEncodedPublicKey, long threadId) { private void resetSession(String hexEncodedPublicKey) {
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context); TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session."); Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session.");
sessionStore.archiveAllSessions(hexEncodedPublicKey); sessionStore.archiveAllSessions(hexEncodedPublicKey);
lokiThreadDatabase.setSessionResetStatus(threadId, LokiThreadSessionResetStatus.REQUEST_RECEIVED); lokiThreadDatabase.setSessionResetStatus(hexEncodedPublicKey, LokiSessionResetStatus.REQUEST_RECEIVED);
Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + "."); Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + ".");
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey); MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
@ -1203,21 +1211,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (lokiMessage.getPreKeyBundleMessage() == null) { return; } if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
int registrationID = TextSecurePreferences.getLocalRegistrationId(context); int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context); LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
if (registrationID <= 0) { return; } if (registrationID <= 0) { return; }
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + "."); Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID); PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle); lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
// If we received a friend request, but we were already friends with the user, reset the session
if (content.isFriendRequest()) {
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
resetSession(content.getSender(), threadID);
// Let our other devices know that we have reset the session
MessageSender.syncContact(context, sender.getAddress());
}
}
} }
private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) { private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {

View File

@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.loki
import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.sms.MessageSender
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
import org.whispersystems.libsignal.protocol.PreKeySignalMessage
class LokiSessionResetImplementation(private val context: Context) : LokiSessionResetProtocol {
override fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
return DatabaseFactory.getLokiThreadDatabase(context).getSessionResetStatus(hexEncodedPublicKey)
}
override fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
return DatabaseFactory.getLokiThreadDatabase(context).setSessionResetStatus(hexEncodedPublicKey, sessionResetStatus)
}
override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
if (oldSessionResetStatus == LokiSessionResetStatus.IN_PROGRESS) {
// Send a message back to the contact to finalise session reset
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey)
}
// TODO: Show session reset succeed message
}
override fun validatePreKeySignalMessage(sender: String, message: PreKeySignalMessage) {
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getPreKeyRecord(sender)
check(preKeyRecord != null) { "Received a background message from a user without an associated pre key record." }
check(preKeyRecord.id == (message.preKeyId ?: -1)) { "Received a background message from an unknown source." }
}
}

View File

@ -10,11 +10,11 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.* import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChat
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol { class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
@ -23,11 +23,11 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
companion object { companion object {
private val friendRequestTableName = "loki_thread_friend_request_database" private val friendRequestTableName = "loki_thread_friend_request_database"
private val sessionResetTableName = "loki_thread_session_reset_database" private val sessionResetTableName = "loki_thread_session_reset_database"
public val publicChatTableName = "loki_public_chat_database" val publicChatTableName = "loki_public_chat_database"
public val threadID = "thread_id" val threadID = "thread_id"
private val friendRequestStatus = "friend_request_status" private val friendRequestStatus = "friend_request_status"
private val sessionResetStatus = "session_reset_status" private val sessionResetStatus = "session_reset_status"
public val publicChat = "public_chat" val publicChat = "public_chat"
@JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);" @JvmStatic val createFriendRequestTableCommand = "CREATE TABLE $friendRequestTableName ($threadID INTEGER PRIMARY KEY, $friendRequestStatus INTEGER DEFAULT 0);"
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);" @JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTableName ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);" @JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTableName ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
@ -79,19 +79,21 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|| friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED
} }
override fun getSessionResetStatus(threadID: Long): LokiThreadSessionResetStatus { fun getSessionResetStatus(hexEncodedPublicKey: String): LokiSessionResetStatus {
val threadID = getThreadID(hexEncodedPublicKey)
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor -> val result = database.get(sessionResetTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
cursor.getInt(sessionResetStatus) cursor.getInt(sessionResetStatus)
} }
return if (result != null) { return if (result != null) {
LokiThreadSessionResetStatus.values().first { it.rawValue == result } LokiSessionResetStatus.values().first { it.rawValue == result }
} else { } else {
LokiThreadSessionResetStatus.NONE LokiSessionResetStatus.NONE
} }
} }
override fun setSessionResetStatus(threadID: Long, sessionResetStatus: LokiThreadSessionResetStatus) { fun setSessionResetStatus(hexEncodedPublicKey: String, sessionResetStatus: LokiSessionResetStatus) {
val threadID = getThreadID(hexEncodedPublicKey)
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2) val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID) contentValues.put(Companion.threadID, threadID)