Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session Protocol V2 #396

Merged
merged 14 commits into from Jan 10, 2021
3 changes: 3 additions & 0 deletions build.gradle
Expand Up @@ -12,6 +12,7 @@ buildscript {
classpath "com.android.tools.build:gradle:4.0.1"
classpath files('libs/gradle-witness.jar')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.gms:google-services:4.3.3"
}
}
Expand All @@ -22,6 +23,7 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'witness'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlinx-serialization'

repositories {
mavenLocal()
Expand Down Expand Up @@ -154,6 +156,7 @@ dependencies {
implementation "org.whispersystems:signal-service-android:2.13.2" // Run ./gradlew install from session-android-service to install
implementation "org.whispersystems:curve25519-java:0.5.0"
// Remote:
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
implementation "com.goterl.lazycode:lazysodium-android:4.2.0@aar"
implementation "net.java.dev.jna:jna:5.5.0@aar"
implementation "com.google.protobuf:protobuf-java:2.5.0"
Expand Down
Expand Up @@ -161,6 +161,7 @@
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
Expand Down Expand Up @@ -1187,7 +1188,18 @@ private void handleLeavePushGroup() {
builder.setTitle(getString(R.string.ConversationActivity_leave_group));
builder.setIconAttribute(R.attr.dialog_info_icon);
builder.setCancelable(true);
builder.setMessage(getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group));

GroupRecord group = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()).orNull();
List<Address> admins = group.getAdmins();
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
String message = getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group);
for (Address admin : admins) {
if (admin.toPhoneString().equals(userPublicKey)) {
message = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone.";
}
}

builder.setMessage(message);
builder.setPositiveButton(R.string.yes, (dialog, which) -> {
Recipient groupRecipient = getRecipient();
String groupPublicKey;
Expand All @@ -1201,7 +1213,7 @@ private void handleLeavePushGroup() {
}
try {
if (isSSKBasedClosedGroup) {
ClosedGroupsProtocol.leave(this, groupPublicKey);
ClosedGroupsProtocolV2.leave(this, groupPublicKey);
initializeEnabledCheck();
} else if (ClosedGroupsProtocol.leaveLegacyGroup(this, groupRecipient)) {
initializeEnabledCheck();
Expand Down
Expand Up @@ -24,7 +24,6 @@
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
Expand All @@ -38,12 +37,14 @@
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiPreKeyBundleDatabase;
import org.thoughtcrime.securesms.loki.database.LokiPreKeyRecordDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.GroupUtil;
Expand Down Expand Up @@ -95,8 +96,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV16 = 37;
private static final int lokiV17 = 38;
private static final int lokiV18_CLEAR_BG_POLL_JOBS = 39;
private static final int lokiV19 = 40;

private static final int DATABASE_VERSION = lokiV18_CLEAR_BG_POLL_JOBS; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV19;
private static final String DATABASE_NAME = "signal.db";

private final Context context;
Expand Down Expand Up @@ -159,6 +161,8 @@ public void onCreate(SQLiteDatabase db) {
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand());
db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand());
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
Expand Down Expand Up @@ -651,6 +655,12 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DELETE FROM constraint_spec WHERE factory_key = 'BackgroundPollJob'");
}

if (oldVersion < lokiV19) {
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
ClosedGroupsMigration.INSTANCE.perform(db);
}

db.setTransactionSuccessful();
} finally {
db.endTransaction();
Expand Down
2 changes: 2 additions & 0 deletions src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java
Expand Up @@ -16,6 +16,7 @@
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJobV2;
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.SessionRequestMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob;
Expand All @@ -34,6 +35,7 @@ public static Map<String, Job.Factory> getJobFactories(@NonNull Application appl
put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory());
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
put(ClosedGroupUpdateMessageSendJob.KEY, new ClosedGroupUpdateMessageSendJob.Factory());
put(ClosedGroupUpdateMessageSendJobV2.KEY, new ClosedGroupUpdateMessageSendJobV2.Factory());
put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory());
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory());
Expand Down
12 changes: 9 additions & 3 deletions src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
Expand Up @@ -66,9 +66,11 @@
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
Expand Down Expand Up @@ -126,6 +128,7 @@
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.loki.api.crypto.SessionProtocol;
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI;
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
Expand Down Expand Up @@ -260,7 +263,8 @@ private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Opt
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context);
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator());

SignalServiceContent content = cipher.decrypt(envelope);

Expand All @@ -283,8 +287,8 @@ private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Opt
MultiDeviceProtocol.handleUnlinkingRequestIfNeeded(context, content);
} else {

if (message.getClosedGroupUpdate().isPresent()) {
ClosedGroupsProtocol.handleSharedSenderKeysUpdate(context, message.getClosedGroupUpdate().get(), content.getSender());
if (message.getClosedGroupUpdateV2().isPresent()) {
ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupUpdateV2().get(), envelope.getSource(), content.getSender());
}

if (message.isEndSession()) {
Expand Down Expand Up @@ -380,6 +384,8 @@ private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Opt
Log.i(TAG, "Dropping UD message from self.");
} catch (IOException e) {
Log.i(TAG, "IOException during message decryption.");
} catch (SessionProtocol.Exception e) {
Log.i(TAG, "Couldn't handle message due to error: " + e.getDescription());
}
}

Expand Down
Expand Up @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.utilities.fadeIn
import org.thoughtcrime.securesms.loki.utilities.fadeOut
import org.thoughtcrime.securesms.mms.GlideApp
Expand Down Expand Up @@ -122,7 +123,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
isLoading = true
loader.fadeIn()
ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
ClosedGroupsProtocolV2.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
loader.fadeOut()
isLoading = false
val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
Expand Down
Expand Up @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
Expand All @@ -20,6 +21,7 @@ import kotlinx.android.synthetic.main.activity_edit_closed_group.*
import kotlinx.android.synthetic.main.activity_edit_closed_group.loader
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
Expand All @@ -28,6 +30,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.utilities.fadeIn
import org.thoughtcrime.securesms.loki.utilities.fadeOut
import org.thoughtcrime.securesms.mms.GlideApp
Expand Down Expand Up @@ -161,8 +164,9 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
this.members.addAll(members)
memberListAdapter.setMembers(members)

val userPublicKey = TextSecurePreferences.getLocalNumber(this)
memberListAdapter.setLockedMembers(arrayListOf(userPublicKey))
val admins = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().admins.map { it.toPhoneString() }.toMutableSet()
admins.remove(TextSecurePreferences.getLocalNumber(this))
memberListAdapter.setLockedMembers(admins)

mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
Expand Down Expand Up @@ -211,7 +215,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
}

private fun commitChanges() {
val hasMemberListChanges = members != originalMembers
val hasMemberListChanges = (members != originalMembers)

if (!hasNameChanged && !hasMemberListChanges) {
return finish()
Expand Down Expand Up @@ -245,16 +249,31 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
return Toast.makeText(this, R.string.activity_edit_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
}

val userPublicKey = TextSecurePreferences.getLocalNumber(this)
val userAsRecipient = Recipient.from(this, Address.fromSerialized(userPublicKey), false)

if (!members.contains(userAsRecipient) && !members.map { it.address.toPhoneString() }.containsAll(originalMembers.minus(userPublicKey))) {
val message = "Can't leave while adding or removing other members."
return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
}

if (isSSKBasedClosedGroup) {
isLoading = true
loader.fadeIn()
ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name).successUi {
val promise: Promise<Unit, Exception>
if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
promise = ClosedGroupsProtocolV2.leave(this, groupPublicKey!!)
} else {
promise = ClosedGroupsProtocolV2.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name)
}
promise.successUi {
loader.fadeOut()
isLoading = false
finish()
}.failUi { exception ->
val message = if (exception is ClosedGroupsProtocol.Error) exception.description else "An error occurred"
Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
loader.fadeOut()
isLoading = false
}
} else {
Expand Down
17 changes: 14 additions & 3 deletions src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
Expand Up @@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
import org.thoughtcrime.securesms.loki.dialogs.*
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.loki.views.ConversationView
Expand Down Expand Up @@ -305,13 +306,23 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val threadID = thread.threadId
val recipient = thread.recipient
val threadDB = DatabaseFactory.getThreadDatabase(this)
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
val isClosedGroup = recipient.address.isClosedGroup
val dialogMessage: String
if (recipient.isGroupRecipient) {
val group = DatabaseFactory.getGroupDatabase(this).getGroup(recipient.address.toString()).orNull()
if (group != null && group.admins.map { it.toPhoneString() }.contains(TextSecurePreferences.getLocalNumber(this))) {
dialogMessage = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."
} else {
dialogMessage = resources.getString(R.string.activity_home_leave_group_dialog_message)
}
} else {
dialogMessage = resources.getString(R.string.activity_home_delete_conversation_dialog_message)
}
val dialog = AlertDialog.Builder(this)
dialog.setMessage(dialogMessage)
dialog.setPositiveButton(R.string.yes) { _, _ -> lifecycleScope.launch(Dispatchers.Main) {
val context = this@HomeActivity as Context

val isClosedGroup = recipient.address.isClosedGroup
// Send a leave group message if this is an active closed group
if (isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) {
var isSSKBasedClosedGroup: Boolean
Expand All @@ -324,7 +335,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
isSSKBasedClosedGroup = false
}
if (isSSKBasedClosedGroup) {
ClosedGroupsProtocol.leave(context, groupPublicKey!!)
ClosedGroupsProtocolV2.leave(context, groupPublicKey!!)
} else if (!ClosedGroupsProtocol.leaveLegacyGroup(context, recipient)) {
Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show()
return@launch
Expand Down