feat: add sending group's public key with the target user 1 on 1 message for enc key pair

This commit is contained in:
jubb 2021-02-19 17:33:23 +11:00
parent 67bf41238e
commit 9df35ede14
2 changed files with 25 additions and 96 deletions

View File

@ -27,7 +27,10 @@ import org.session.libsignal.utilities.Hex
import java.util.*
import java.util.concurrent.TimeUnit
class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Parameters, private val destination: String, private val kind: Kind, private val sentTime: Long) : BaseJob(parameters) {
class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Parameters,
private val destination: String,
private val kind: Kind,
private val sentTime: Long) : BaseJob(parameters) {
sealed class Kind {
class New(val publicKey: ByteArray, val name: String, val encryptionKeyPair: ECKeyPair, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
@ -36,7 +39,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
class RemoveMembers(val members: Collection<ByteArray>) : Kind()
class AddMembers(val members: Collection<ByteArray>) : Kind()
class NameChange(val name: String) : Kind()
class EncryptionKeyPair(val wrappers: Collection<KeyPairWrapper>) : Kind() // The new encryption key pair encrypted for each member individually
class EncryptionKeyPair(val wrappers: Collection<KeyPairWrapper>, val targetUser: String?) : Kind() // The new encryption key pair encrypted for each member individually
}
companion object {
@ -116,6 +119,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
builder.putString("kind", "EncryptionKeyPair")
val wrappers = kind.wrappers.joinToString(" - ") { Json.encodeToString(it) }
builder.putString("wrappers", wrappers)
builder.putString("targetUser", kind.targetUser)
}
}
return builder.build()
@ -146,7 +150,8 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
}
"EncryptionKeyPair" -> {
val wrappers: Collection<KeyPairWrapper> = data.getString("wrappers").split(" - ").map { Json.decodeFromString(it) }
kind = Kind.EncryptionKeyPair(wrappers)
val targetUser = data.getString("targetUser")
kind = Kind.EncryptionKeyPair(wrappers, targetUser)
}
"RemoveMembers" -> {
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
@ -170,6 +175,11 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
}
public override fun onRun() {
val sendDestination = if (kind is Kind.EncryptionKeyPair && kind.targetUser != null) {
kind.targetUser
} else {
destination
}
val contentMessage = SignalServiceProtos.Content.newBuilder()
val dataMessage = SignalServiceProtos.DataMessage.newBuilder()
val closedGroupUpdate = SignalServiceProtos.ClosedGroupUpdateV2.newBuilder()
@ -193,6 +203,9 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
is Kind.EncryptionKeyPair -> {
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR
closedGroupUpdate.addAllWrappers(kind.wrappers.map { it.toProto() })
if (kind.targetUser != null) {
closedGroupUpdate.publicKey = ByteString.copyFrom(kind.targetUser.toByteArray())
}
}
Kind.Leave -> {
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT
@ -214,8 +227,8 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
contentMessage.dataMessage = dataMessage.build()
val serializedContentMessage = contentMessage.build().toByteArray()
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(destination)
val recipient = recipient(context, destination)
val address = SignalServiceAddress(sendDestination)
val recipient = recipient(context, sendDestination)
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
val ttl = when (kind) {
is Kind.EncryptionKeyPair -> 4 * 24 * 60 * 60 * 1000

View File

@ -2,11 +2,9 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context
import android.util.Log
import androidx.annotation.WorkerThread
import com.google.protobuf.ByteString
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import nl.komponents.kovenant.task
import org.session.libsignal.libsignal.ecc.Curve
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
@ -241,89 +239,7 @@ object ClosedGroupsProtocolV2 {
groupDB.updateTitle(groupID, newName)
}
fun update(context: Context, groupPublicKey: String, members: Collection<String>, name: String): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
ThreadUtils.queue {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = doubleEncodeGroupID(groupPublicKey)
val group = groupDB.getGroup(groupID).orNull()
if (group == null) {
Log.d("Loki", "Can't update nonexistent closed group.")
return@queue deferred.reject(Error.NoThread)
}
val sentTime = System.currentTimeMillis()
val oldMembers = group.members.map { it.serialize() }.toSet()
val newMembers = members.minus(oldMembers)
val membersAsData = members.map { Hex.fromStringCondensed(it) }
val admins = group.admins.map { it.serialize() }
val adminsAsData = admins.map { Hex.fromStringCondensed(it) }
val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
if (encryptionKeyPair == null) {
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
return@queue deferred.reject(Error.NoKeyPair)
}
val removedMembers = oldMembers.minus(members)
if (removedMembers.contains(admins.first()) && members.isNotEmpty()) {
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
return@queue deferred.reject(Error.InvalidUpdate)
}
val isUserLeaving = removedMembers.contains(userPublicKey)
if (isUserLeaving && members.isNotEmpty()) {
if (removedMembers.count() != 1 || newMembers.isNotEmpty()) {
Log.d("Loki", "Can't remove self and add or remove others simultaneously.")
return@queue deferred.reject(Error.InvalidUpdate)
}
}
// Send the update to the group
@Suppress("NAME_SHADOWING")
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.Update(name, membersAsData)
@Suppress("NAME_SHADOWING")
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, closedGroupUpdateKind, sentTime)
job.setContext(context)
job.onRun() // Run the job immediately
if (isUserLeaving) {
// Remove the group private key and unsubscribe from PNs
apiDB.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
apiDB.removeClosedGroupPublicKey(groupPublicKey)
// Mark the group as inactive
groupDB.setActive(groupID, false)
groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
// Notify the PN server
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
} else {
// Generate and distribute a new encryption key pair if needed
val wasAnyUserRemoved = removedMembers.isNotEmpty()
val isCurrentUserAdmin = admins.contains(userPublicKey)
if (wasAnyUserRemoved && isCurrentUserAdmin) {
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, members.minus(newMembers))
}
// Send closed group update messages to any new members individually
for (member in newMembers) {
@Suppress("NAME_SHADOWING")
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData)
@Suppress("NAME_SHADOWING")
val job = ClosedGroupUpdateMessageSendJobV2(member, closedGroupUpdateKind, sentTime)
ApplicationContext.getInstance(context).jobManager.add(job)
}
}
// Update the group
groupDB.updateTitle(groupID, name)
if (!isUserLeaving) {
// The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
}
// Notify the user
val infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID, sentTime)
deferred.resolve(Unit)
}
return deferred.promise
}
fun generateAndSendNewEncryptionKeyPair(context: Context, groupPublicKey: String, targetMembers: Collection<String>) {
private fun generateAndSendNewEncryptionKeyPair(context: Context, groupPublicKey: String, targetMembers: Collection<String>) {
// Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
@ -352,7 +268,7 @@ object ClosedGroupsProtocolV2 {
pendingKeyPair[groupPublicKey] = Optional.absent()
}
private fun sendEncryptionKeyPair(context: Context, target: String, newKeyPair: ECKeyPair, targetMembers: Collection<String>, force: Boolean = true) {
private fun sendEncryptionKeyPair(context: Context, groupPublicKey: String, newKeyPair: ECKeyPair, targetMembers: Collection<String>, targetUser: String? = null, force: Boolean = true) {
val proto = SignalServiceProtos.KeyPair.newBuilder()
proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded())
proto.privateKey = ByteString.copyFrom(newKeyPair.privateKey.serialize())
@ -361,7 +277,7 @@ object ClosedGroupsProtocolV2 {
val ciphertext = SessionProtocolImpl(context).encrypt(plaintext, publicKey)
ClosedGroupUpdateMessageSendJobV2.KeyPairWrapper(publicKey, ciphertext)
}
val job = ClosedGroupUpdateMessageSendJobV2(target, ClosedGroupUpdateMessageSendJobV2.Kind.EncryptionKeyPair(wrappers), System.currentTimeMillis())
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.EncryptionKeyPair(wrappers, targetUser), System.currentTimeMillis())
if (force) {
job.setContext(context)
job.onRun() // Run the job immediately
@ -515,17 +431,17 @@ object ClosedGroupsProtocolV2 {
Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
return
}
// Check common group update logic
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
return
}
val name = group.title
// Check common group update logic
val members = group.members.map { it.serialize() }
val admins = group.admins.map { it.serialize() }
// Users that are part of this remove update
// Users that are part of this add update
val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
// newMembers to save is old members minus removed members
// newMembers to save is old members plus members included in this update
val newMembers = members + updateMembers
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
@ -543,7 +459,7 @@ object ClosedGroupsProtocolV2 {
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
} else {
for (user in updateMembers) {
sendEncryptionKeyPair(context, user, encryptionKeyPair, newMembers, false)
sendEncryptionKeyPair(context, groupPublicKey, encryptionKeyPair, newMembers, targetUser = user, force = false)
}
}
}