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.*
import java.util.concurrent.TimeUnit 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 { sealed class Kind {
class New(val publicKey: ByteArray, val name: String, val encryptionKeyPair: ECKeyPair, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : 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 RemoveMembers(val members: Collection<ByteArray>) : Kind()
class AddMembers(val members: Collection<ByteArray>) : Kind() class AddMembers(val members: Collection<ByteArray>) : Kind()
class NameChange(val name: String) : 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 { companion object {
@ -116,6 +119,7 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
builder.putString("kind", "EncryptionKeyPair") builder.putString("kind", "EncryptionKeyPair")
val wrappers = kind.wrappers.joinToString(" - ") { Json.encodeToString(it) } val wrappers = kind.wrappers.joinToString(" - ") { Json.encodeToString(it) }
builder.putString("wrappers", wrappers) builder.putString("wrappers", wrappers)
builder.putString("targetUser", kind.targetUser)
} }
} }
return builder.build() return builder.build()
@ -146,7 +150,8 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
} }
"EncryptionKeyPair" -> { "EncryptionKeyPair" -> {
val wrappers: Collection<KeyPairWrapper> = data.getString("wrappers").split(" - ").map { Json.decodeFromString(it) } 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" -> { "RemoveMembers" -> {
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) } val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
@ -170,6 +175,11 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
} }
public override fun onRun() { public override fun onRun() {
val sendDestination = if (kind is Kind.EncryptionKeyPair && kind.targetUser != null) {
kind.targetUser
} else {
destination
}
val contentMessage = SignalServiceProtos.Content.newBuilder() val contentMessage = SignalServiceProtos.Content.newBuilder()
val dataMessage = SignalServiceProtos.DataMessage.newBuilder() val dataMessage = SignalServiceProtos.DataMessage.newBuilder()
val closedGroupUpdate = SignalServiceProtos.ClosedGroupUpdateV2.newBuilder() val closedGroupUpdate = SignalServiceProtos.ClosedGroupUpdateV2.newBuilder()
@ -193,6 +203,9 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
is Kind.EncryptionKeyPair -> { is Kind.EncryptionKeyPair -> {
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR
closedGroupUpdate.addAllWrappers(kind.wrappers.map { it.toProto() }) closedGroupUpdate.addAllWrappers(kind.wrappers.map { it.toProto() })
if (kind.targetUser != null) {
closedGroupUpdate.publicKey = ByteString.copyFrom(kind.targetUser.toByteArray())
}
} }
Kind.Leave -> { Kind.Leave -> {
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT
@ -214,8 +227,8 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
contentMessage.dataMessage = dataMessage.build() contentMessage.dataMessage = dataMessage.build()
val serializedContentMessage = contentMessage.build().toByteArray() val serializedContentMessage = contentMessage.build().toByteArray()
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(destination) val address = SignalServiceAddress(sendDestination)
val recipient = recipient(context, destination) val recipient = recipient(context, sendDestination)
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient) val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
val ttl = when (kind) { val ttl = when (kind) {
is Kind.EncryptionKeyPair -> 4 * 24 * 60 * 60 * 1000 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.content.Context
import android.util.Log import android.util.Log
import androidx.annotation.WorkerThread
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred import nl.komponents.kovenant.deferred
import nl.komponents.kovenant.task
import org.session.libsignal.libsignal.ecc.Curve import org.session.libsignal.libsignal.ecc.Curve
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey
@ -241,89 +239,7 @@ object ClosedGroupsProtocolV2 {
groupDB.updateTitle(groupID, newName) groupDB.updateTitle(groupID, newName)
} }
fun update(context: Context, groupPublicKey: String, members: Collection<String>, name: String): Promise<Unit, Exception> { private fun generateAndSendNewEncryptionKeyPair(context: Context, groupPublicKey: String, targetMembers: Collection<String>) {
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>) {
// Prepare // Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context) val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
@ -352,7 +268,7 @@ object ClosedGroupsProtocolV2 {
pendingKeyPair[groupPublicKey] = Optional.absent() 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() val proto = SignalServiceProtos.KeyPair.newBuilder()
proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) proto.publicKey = ByteString.copyFrom(newKeyPair.publicKey.serialize().removing05PrefixIfNeeded())
proto.privateKey = ByteString.copyFrom(newKeyPair.privateKey.serialize()) proto.privateKey = ByteString.copyFrom(newKeyPair.privateKey.serialize())
@ -361,7 +277,7 @@ object ClosedGroupsProtocolV2 {
val ciphertext = SessionProtocolImpl(context).encrypt(plaintext, publicKey) val ciphertext = SessionProtocolImpl(context).encrypt(plaintext, publicKey)
ClosedGroupUpdateMessageSendJobV2.KeyPairWrapper(publicKey, ciphertext) 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) { if (force) {
job.setContext(context) job.setContext(context)
job.onRun() // Run the job immediately 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.") Log.d("Loki", "Ignoring closed group info message for nonexistent or inactive group.")
return return
} }
// Check common group update logic
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) { if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
return return
} }
val name = group.title val name = group.title
// Check common group update logic
val members = group.members.map { it.serialize() } val members = group.members.map { it.serialize() }
val admins = group.admins.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() } 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 val newMembers = members + updateMembers
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) 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.") Log.d("Loki", "Couldn't get encryption key pair for closed group.")
} else { } else {
for (user in updateMembers) { for (user in updateMembers) {
sendEncryptionKeyPair(context, user, encryptionKeyPair, newMembers, false) sendEncryptionKeyPair(context, groupPublicKey, encryptionKeyPair, newMembers, targetUser = user, force = false)
} }
} }
} }