feat: update libsession-util and get signCallbacks working for authenticated retrieve, handling incoming messages in new closed groups
This commit is contained in:
parent
fb8b146703
commit
ae7c27c2e0
|
@ -1209,25 +1209,26 @@ open class Storage(
|
||||||
configFactory.getGroupMemberConfig(SessionId.from(groupPublicKey))?.use { it.all() }?.toList() ?: emptyList()
|
configFactory.getGroupMemberConfig(SessionId.from(groupPublicKey))?.use { it.all() }?.toList() ?: emptyList()
|
||||||
|
|
||||||
override fun acceptClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId) {
|
override fun acceptClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId) {
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(groupId.hexString()), false)
|
val recipient = Recipient.from(context, fromSerialized(groupId.hexString()), false)
|
||||||
val profileManager = SSKEnvironment.shared.profileManager
|
val profileManager = SSKEnvironment.shared.profileManager
|
||||||
val groups = configFactory.userGroups ?: return
|
val groups = configFactory.userGroups ?: return
|
||||||
val closedGroupInfo = GroupInfo.ClosedGroupInfo(
|
val closedGroupInfo = GroupInfo.ClosedGroupInfo(
|
||||||
groupId, byteArrayOf(), authData, PRIORITY_VISIBLE
|
groupId, byteArrayOf(), authData, PRIORITY_VISIBLE
|
||||||
)
|
)
|
||||||
groups.set(closedGroupInfo)
|
groups.set(closedGroupInfo)
|
||||||
|
configFactory.persist(groups, SnodeAPI.nowWithOffset)
|
||||||
profileManager.setName(context, recipient, name)
|
profileManager.setName(context, recipient, name)
|
||||||
setRecipientApprovedMe(recipient, true)
|
setRecipientApprovedMe(recipient, true)
|
||||||
setRecipientApproved(recipient, true)
|
setRecipientApproved(recipient, true)
|
||||||
getOrCreateThreadIdFor(recipient.address)
|
getOrCreateThreadIdFor(recipient.address)
|
||||||
pollerFactory.pollerFor(groupId)?.start()
|
pollerFactory.pollerFor(groupId)?.start()
|
||||||
val invitingAdminAddress = Address.fromSerialized(invitingAdmin.hexString())
|
|
||||||
val inviteResponse = GroupUpdateInviteResponseMessage.newBuilder()
|
val inviteResponse = GroupUpdateInviteResponseMessage.newBuilder()
|
||||||
.setIsApproved(true)
|
.setIsApproved(true)
|
||||||
val responseData = DataMessage.GroupUpdateMessage.newBuilder()
|
val responseData = DataMessage.GroupUpdateMessage.newBuilder()
|
||||||
.setInviteResponse(inviteResponse)
|
.setInviteResponse(inviteResponse)
|
||||||
val responseMessage = GroupUpdated(responseData.build())
|
val responseMessage = GroupUpdated(responseData.build())
|
||||||
MessageSender.send(responseMessage, invitingAdminAddress)
|
// this will fail the first couple of times :)
|
||||||
|
MessageSender.send(responseMessage, fromSerialized(groupId.hexString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setGroupInviteComplete(approved: Boolean, invitee: String, closedGroup: SessionId) {
|
override fun setGroupInviteComplete(approved: Boolean, invitee: String, closedGroup: SessionId) {
|
||||||
|
|
|
@ -21,4 +21,7 @@
|
||||||
<certificates src="@raw/seed3"/>
|
<certificates src="@raw/seed3"/>
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</domain-config>
|
</domain-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="false">public.loki.foundation</domain>
|
||||||
|
</domain-config>
|
||||||
</network-security-config>
|
</network-security-config>
|
|
@ -1 +1 @@
|
||||||
Subproject commit e4b0358a50a65796ac5e2da4938505bc8ebf0313
|
Subproject commit 986caaa28305fa6481238d98e6f7dd096c562336
|
|
@ -1,9 +1,25 @@
|
||||||
package org.session.libsession.messaging.jobs
|
package org.session.libsession.messaging.jobs
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsession.messaging.messages.Destination
|
||||||
|
import org.session.libsession.messaging.messages.control.GroupUpdated
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.utilities.Data
|
import org.session.libsession.messaging.utilities.Data
|
||||||
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.SessionId
|
||||||
|
|
||||||
class InviteContactJob(val groupSessionId: String, val memberSessionId: String): Job {
|
class InviteContactJob(val groupSessionId: String, val memberSessionId: String): Job {
|
||||||
|
|
||||||
|
sealed class InviteError(message: String): Exception(message) {
|
||||||
|
object NO_GROUP_KEYS: InviteError("No group keys config for this group")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY = "InviteContactJob"
|
const val KEY = "InviteContactJob"
|
||||||
private const val GROUP = "group"
|
private const val GROUP = "group"
|
||||||
|
@ -16,7 +32,47 @@ class InviteContactJob(val groupSessionId: String, val memberSessionId: String):
|
||||||
override val maxFailureCount: Int = 1
|
override val maxFailureCount: Int = 1
|
||||||
|
|
||||||
override suspend fun execute(dispatcherName: String) {
|
override suspend fun execute(dispatcherName: String) {
|
||||||
TODO("Not yet implemented")
|
val delegate = delegate ?: return
|
||||||
|
val configs = MessagingModuleConfiguration.shared.configFactory
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
val adminKey = configs.userGroups?.getClosedGroup(groupSessionId)?.adminKey
|
||||||
|
?: return delegate.handleJobFailedPermanently(this, dispatcherName, NullPointerException("No admin key"))
|
||||||
|
val subAccount = configs.getGroupKeysConfig(SessionId.from(groupSessionId))?.use { keys ->
|
||||||
|
keys.makeSubAccount(SessionId.from(memberSessionId))
|
||||||
|
} ?: return delegate.handleJobFailedPermanently(this, dispatcherName, InviteError.NO_GROUP_KEYS)
|
||||||
|
val timestamp = SnodeAPI.nowWithOffset
|
||||||
|
val messageToSign = "INVITE$memberSessionId$timestamp"
|
||||||
|
val signature = SodiumUtilities.sign(messageToSign.toByteArray(), adminKey)
|
||||||
|
val userProfile = storage.getUserProfile()
|
||||||
|
val lokiProfile = LokiProfile.newBuilder()
|
||||||
|
.setDisplayName(userProfile.displayName)
|
||||||
|
if (userProfile.profilePictureURL?.isNotEmpty() == true) {
|
||||||
|
lokiProfile.profilePicture = userProfile.profilePictureURL
|
||||||
|
}
|
||||||
|
val groupInvite = GroupUpdateInviteMessage.newBuilder()
|
||||||
|
.setGroupSessionId(groupSessionId)
|
||||||
|
.setMemberAuthData(ByteString.copyFrom(subAccount))
|
||||||
|
.setAdminSignature(ByteString.copyFrom(signature))
|
||||||
|
.setName(userProfile.displayName)
|
||||||
|
.setProfile(lokiProfile.build())
|
||||||
|
if (userProfile.profileKey?.isNotEmpty() == true) {
|
||||||
|
groupInvite.profileKey = ByteString.copyFrom(userProfile.profileKey)
|
||||||
|
}
|
||||||
|
val message = GroupUpdateMessage.newBuilder()
|
||||||
|
.setInviteMessage(groupInvite)
|
||||||
|
.build()
|
||||||
|
val update = GroupUpdated(message).apply {
|
||||||
|
sentTimestamp = timestamp
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
MessageSender.send(update, Destination.Contact(memberSessionId), false).get()
|
||||||
|
Log.d("InviteContactJob", "Sent invite message successfully")
|
||||||
|
delegate.handleJobSucceeded(this, dispatcherName)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("InviteContactJob", e)
|
||||||
|
delegate.handleJobFailed(this, dispatcherName, e)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(): Data =
|
override fun serialize(): Data =
|
||||||
|
|
|
@ -122,7 +122,11 @@ class JobQueue : JobDelegate {
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
when (val job = queue.receive()) {
|
when (val job = queue.receive()) {
|
||||||
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob, is ConfigurationSyncJob -> {
|
is NotifyPNServerJob,
|
||||||
|
is AttachmentUploadJob,
|
||||||
|
is MessageSendJob,
|
||||||
|
is ConfigurationSyncJob,
|
||||||
|
is InviteContactJob -> {
|
||||||
txQueue.send(job)
|
txQueue.send(job)
|
||||||
}
|
}
|
||||||
is RetrieveProfileAvatarJob,
|
is RetrieveProfileAvatarJob,
|
||||||
|
|
|
@ -6,6 +6,10 @@ import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateM
|
||||||
|
|
||||||
class GroupUpdated(val inner: GroupUpdateMessage): ControlMessage() {
|
class GroupUpdated(val inner: GroupUpdateMessage): ControlMessage() {
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
return true // TODO: add the validation here
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromProto(message: Content): GroupUpdated? =
|
fun fromProto(message: Content): GroupUpdated? =
|
||||||
if (message.hasDataMessage() && message.dataMessage.hasGroupUpdateMessage())
|
if (message.hasDataMessage() && message.dataMessage.hasGroupUpdateMessage())
|
||||||
|
|
|
@ -8,11 +8,12 @@ import androidx.annotation.Nullable;
|
||||||
import org.session.libsession.messaging.calls.CallMessageType;
|
import org.session.libsession.messaging.calls.CallMessageType;
|
||||||
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation;
|
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation;
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
||||||
import org.session.libsession.utilities.Address;
|
|
||||||
import org.session.libsession.messaging.utilities.UpdateMessageData;
|
import org.session.libsession.messaging.utilities.UpdateMessageData;
|
||||||
|
import org.session.libsession.utilities.Address;
|
||||||
import org.session.libsession.utilities.GroupUtil;
|
import org.session.libsession.utilities.GroupUtil;
|
||||||
import org.session.libsignal.utilities.guava.Optional;
|
|
||||||
import org.session.libsignal.messages.SignalServiceGroup;
|
import org.session.libsignal.messages.SignalServiceGroup;
|
||||||
|
import org.session.libsignal.utilities.Hex;
|
||||||
|
import org.session.libsignal.utilities.guava.Optional;
|
||||||
|
|
||||||
public class IncomingTextMessage implements Parcelable {
|
public class IncomingTextMessage implements Parcelable {
|
||||||
|
|
||||||
|
@ -78,7 +79,14 @@ public class IncomingTextMessage implements Parcelable {
|
||||||
this.hasMention = hasMention;
|
this.hasMention = hasMention;
|
||||||
|
|
||||||
if (group.isPresent()) {
|
if (group.isPresent()) {
|
||||||
this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get()));
|
SignalServiceGroup groupObject = group.get();
|
||||||
|
if (groupObject.isNewClosedGroup()) {
|
||||||
|
// new closed group 03..etc..
|
||||||
|
this.groupId = Address.fromSerialized(Hex.toStringCondensed(groupObject.getGroupId()));
|
||||||
|
} else {
|
||||||
|
// old closed group or open group
|
||||||
|
this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.groupId = null;
|
this.groupId = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,8 @@ object MessageReceiver {
|
||||||
MessageRequestResponse.fromProto(proto) ?:
|
MessageRequestResponse.fromProto(proto) ?:
|
||||||
CallMessage.fromProto(proto) ?:
|
CallMessage.fromProto(proto) ?:
|
||||||
SharedConfigurationMessage.fromProto(proto) ?:
|
SharedConfigurationMessage.fromProto(proto) ?:
|
||||||
VisibleMessage.fromProto(proto) ?:
|
GroupUpdated.fromProto(proto) ?:
|
||||||
GroupUpdated.fromProto(proto) ?: run {
|
VisibleMessage.fromProto(proto) ?: run {
|
||||||
throw Error.UnknownMessage
|
throw Error.UnknownMessage
|
||||||
}
|
}
|
||||||
val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString() }
|
val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString() }
|
||||||
|
|
|
@ -169,6 +169,9 @@ object MessageSender {
|
||||||
val groupKeys = configFactory.getGroupKeysConfig(SessionId.from(destination.publicKey)) ?: throw Error.NoKeyPair
|
val groupKeys = configFactory.getGroupKeysConfig(SessionId.from(destination.publicKey)) ?: throw Error.NoKeyPair
|
||||||
val envelope = MessageWrapper.createEnvelope(kind, message.sentTimestamp!!, senderPublicKey, proto.build().toByteArray())
|
val envelope = MessageWrapper.createEnvelope(kind, message.sentTimestamp!!, senderPublicKey, proto.build().toByteArray())
|
||||||
groupKeys.use { keys ->
|
groupKeys.use { keys ->
|
||||||
|
if (keys.keys().isEmpty()) {
|
||||||
|
throw Error.EncryptionFailed
|
||||||
|
}
|
||||||
keys.encrypt(envelope.toByteArray())
|
keys.encrypt(envelope.toByteArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,7 +202,6 @@ object MessageSender {
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val configFactory = MessagingModuleConfiguration.shared.configFactory
|
val configFactory = MessagingModuleConfiguration.shared.configFactory
|
||||||
val userPublicKey = storage.getUserPublicKey()
|
val userPublicKey = storage.getUserPublicKey()
|
||||||
val ourProfile = storage.getUserProfile()
|
|
||||||
|
|
||||||
// recipient will be set later, so initialize it as a function here
|
// recipient will be set later, so initialize it as a function here
|
||||||
val isSelfSend = { message.recipient == userPublicKey }
|
val isSelfSend = { message.recipient == userPublicKey }
|
||||||
|
|
|
@ -70,7 +70,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
||||||
is ReadReceipt -> handleReadReceipt(message)
|
is ReadReceipt -> handleReadReceipt(message)
|
||||||
is TypingIndicator -> handleTypingIndicator(message)
|
is TypingIndicator -> handleTypingIndicator(message)
|
||||||
is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message)
|
is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message)
|
||||||
is GroupUpdated -> handleGroupUpdated(message, closedGroup!!)
|
is GroupUpdated -> handleGroupUpdated(message, closedGroup)
|
||||||
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
|
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
|
||||||
is DataExtractionNotification -> handleDataExtractionNotification(message)
|
is DataExtractionNotification -> handleDataExtractionNotification(message)
|
||||||
is ConfigurationMessage -> handleConfigurationMessage(message)
|
is ConfigurationMessage -> handleConfigurationMessage(message)
|
||||||
|
@ -519,10 +519,13 @@ private fun ClosedGroupControlMessage.getPublicKey(): String = kind!!.let { when
|
||||||
is ClosedGroupControlMessage.Kind.NameChange -> groupPublicKey!!
|
is ClosedGroupControlMessage.Kind.NameChange -> groupPublicKey!!
|
||||||
}}
|
}}
|
||||||
|
|
||||||
private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated, closedGroup: SessionId) {
|
private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated, closedGroup: SessionId?) {
|
||||||
|
if (closedGroup == null && !message.inner.hasInviteMessage()) { // TODO: add all the cases for this
|
||||||
|
throw NullPointerException("Message wasn't polled from a closed group!")
|
||||||
|
}
|
||||||
when {
|
when {
|
||||||
message.inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message)
|
message.inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message)
|
||||||
message.inner.hasInviteResponse() -> handleInviteResponse(message, closedGroup)
|
message.inner.hasInviteResponse() -> handleInviteResponse(message, closedGroup!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import network.loki.messenger.libsession_util.GroupInfoConfig
|
||||||
import network.loki.messenger.libsession_util.GroupKeysConfig
|
import network.loki.messenger.libsession_util.GroupKeysConfig
|
||||||
import network.loki.messenger.libsession_util.GroupMembersConfig
|
import network.loki.messenger.libsession_util.GroupMembersConfig
|
||||||
import network.loki.messenger.libsession_util.util.GroupInfo
|
import network.loki.messenger.libsession_util.util.GroupInfo
|
||||||
|
import network.loki.messenger.libsession_util.util.GroupInfo.ClosedGroupInfo.Companion.isAuthData
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
|
@ -30,9 +31,9 @@ class ClosedGroupPoller(private val executor: CoroutineScope,
|
||||||
private val configFactoryProtocol: ConfigFactoryProtocol) {
|
private val configFactoryProtocol: ConfigFactoryProtocol) {
|
||||||
|
|
||||||
data class ParsedRawMessage(
|
data class ParsedRawMessage(
|
||||||
val data: ByteArray,
|
val data: ByteArray,
|
||||||
val hash: String,
|
val hash: String,
|
||||||
val timestamp: Long
|
val timestamp: Long
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
@ -69,7 +70,7 @@ class ClosedGroupPoller(private val executor: CoroutineScope,
|
||||||
if (ENABLE_LOGGING) Log.d("ClosedGroupPoller", "Starting closed group poller for ${closedGroupSessionId.hexString().take(4)}")
|
if (ENABLE_LOGGING) Log.d("ClosedGroupPoller", "Starting closed group poller for ${closedGroupSessionId.hexString().take(4)}")
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = executor.launch(Dispatchers.IO) {
|
job = executor.launch(Dispatchers.IO) {
|
||||||
val closedGroups = configFactoryProtocol.userGroups?: return@launch
|
val closedGroups = configFactoryProtocol.userGroups ?: return@launch
|
||||||
isRunning = true
|
isRunning = true
|
||||||
while (isActive && isRunning) {
|
while (isActive && isRunning) {
|
||||||
val group = closedGroups.getClosedGroup(closedGroupSessionId.hexString()) ?: break
|
val group = closedGroups.getClosedGroup(closedGroupSessionId.hexString()) ?: break
|
||||||
|
@ -95,7 +96,8 @@ class ClosedGroupPoller(private val executor: CoroutineScope,
|
||||||
try {
|
try {
|
||||||
val snode = SnodeAPI.getSingleTargetSnode(closedGroupSessionId.hexString()).get()
|
val snode = SnodeAPI.getSingleTargetSnode(closedGroupSessionId.hexString()).get()
|
||||||
val info = configFactoryProtocol.getGroupInfoConfig(closedGroupSessionId) ?: return null
|
val info = configFactoryProtocol.getGroupInfoConfig(closedGroupSessionId) ?: return null
|
||||||
val members = configFactoryProtocol.getGroupMemberConfig(closedGroupSessionId) ?: return null
|
val members = configFactoryProtocol.getGroupMemberConfig(closedGroupSessionId)
|
||||||
|
?: return null
|
||||||
val keys = configFactoryProtocol.getGroupKeysConfig(closedGroupSessionId) ?: return null
|
val keys = configFactoryProtocol.getGroupKeysConfig(closedGroupSessionId) ?: return null
|
||||||
|
|
||||||
val hashesToExtend = mutableSetOf<String>()
|
val hashesToExtend = mutableSetOf<String>()
|
||||||
|
@ -109,53 +111,58 @@ class ClosedGroupPoller(private val executor: CoroutineScope,
|
||||||
val membersIndex = 2
|
val membersIndex = 2
|
||||||
val messageIndex = 3
|
val messageIndex = 3
|
||||||
|
|
||||||
|
val authData = group.signingKey()
|
||||||
|
val signCallback = if (isAuthData(authData)) {
|
||||||
|
SnodeAPI.subkeyCallback(authData, keys, false)
|
||||||
|
} else SnodeAPI.signingKeyCallback(authData)
|
||||||
|
|
||||||
val messagePoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
val messagePoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
||||||
snode,
|
snode,
|
||||||
closedGroupSessionId.hexString(),
|
closedGroupSessionId.hexString(),
|
||||||
Namespace.CLOSED_GROUP_MESSAGES(),
|
Namespace.CLOSED_GROUP_MESSAGES(),
|
||||||
maxSize = null,
|
maxSize = null,
|
||||||
group.signingKey()
|
signCallback
|
||||||
) ?: return null
|
) ?: return null
|
||||||
val infoPoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
val infoPoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
||||||
snode,
|
snode,
|
||||||
closedGroupSessionId.hexString(),
|
closedGroupSessionId.hexString(),
|
||||||
info.namespace(),
|
info.namespace(),
|
||||||
maxSize = null,
|
maxSize = null,
|
||||||
group.signingKey()
|
signCallback
|
||||||
) ?: return null
|
) ?: return null
|
||||||
val membersPoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
val membersPoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
||||||
snode,
|
snode,
|
||||||
closedGroupSessionId.hexString(),
|
closedGroupSessionId.hexString(),
|
||||||
members.namespace(),
|
members.namespace(),
|
||||||
maxSize = null,
|
maxSize = null,
|
||||||
group.signingKey()
|
signCallback
|
||||||
) ?: return null
|
) ?: return null
|
||||||
val keysPoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
val keysPoll = SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
||||||
snode,
|
snode,
|
||||||
closedGroupSessionId.hexString(),
|
closedGroupSessionId.hexString(),
|
||||||
keys.namespace(),
|
keys.namespace(),
|
||||||
maxSize = null,
|
maxSize = null,
|
||||||
group.signingKey()
|
signCallback
|
||||||
) ?: return null
|
) ?: return null
|
||||||
|
|
||||||
val requests = mutableListOf(keysPoll, infoPoll, membersPoll, messagePoll)
|
val requests = mutableListOf(keysPoll, infoPoll, membersPoll, messagePoll)
|
||||||
|
|
||||||
if (hashesToExtend.isNotEmpty()) {
|
if (hashesToExtend.isNotEmpty()) {
|
||||||
SnodeAPI.buildAuthenticatedAlterTtlBatchRequest(
|
SnodeAPI.buildAuthenticatedAlterTtlBatchRequest(
|
||||||
messageHashes = hashesToExtend.toList(),
|
messageHashes = hashesToExtend.toList(),
|
||||||
publicKey = closedGroupSessionId.hexString(),
|
publicKey = closedGroupSessionId.hexString(),
|
||||||
signingKey = group.signingKey(),
|
signingKey = group.signingKey(),
|
||||||
newExpiry = SnodeAPI.nowWithOffset + 14.days.inWholeMilliseconds,
|
newExpiry = SnodeAPI.nowWithOffset + 14.days.inWholeMilliseconds,
|
||||||
extend = true
|
extend = true
|
||||||
)?.let { extensionRequest ->
|
)?.let { extensionRequest ->
|
||||||
requests += extensionRequest
|
requests += extensionRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pollResult = SnodeAPI.getRawBatchResponse(
|
val pollResult = SnodeAPI.getRawBatchResponse(
|
||||||
snode,
|
snode,
|
||||||
closedGroupSessionId.hexString(),
|
closedGroupSessionId.hexString(),
|
||||||
requests
|
requests
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
// if poll result body is null here we don't have any things ig
|
// if poll result body is null here we don't have any things ig
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.goterl.lazysodium.SodiumAndroid
|
||||||
import com.goterl.lazysodium.interfaces.AEAD
|
import com.goterl.lazysodium.interfaces.AEAD
|
||||||
import com.goterl.lazysodium.interfaces.GenericHash
|
import com.goterl.lazysodium.interfaces.GenericHash
|
||||||
import com.goterl.lazysodium.interfaces.Hash
|
import com.goterl.lazysodium.interfaces.Hash
|
||||||
|
import com.goterl.lazysodium.interfaces.Sign
|
||||||
import com.goterl.lazysodium.utils.Key
|
import com.goterl.lazysodium.utils.Key
|
||||||
import com.goterl.lazysodium.utils.KeyPair
|
import com.goterl.lazysodium.utils.KeyPair
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
|
@ -237,4 +238,15 @@ object SodiumUtilities {
|
||||||
return sodium.cryptoSignVerifyDetached(signature, messageToVerify, messageToVerify.size, publicKey)
|
return sodium.cryptoSignVerifyDetached(signature, messageToVerify, messageToVerify.size, publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For signing
|
||||||
|
*/
|
||||||
|
fun sign(message: ByteArray, signingKey: ByteArray): ByteArray {
|
||||||
|
val signature = ByteArray(Sign.BYTES)
|
||||||
|
|
||||||
|
if (!sodium.cryptoSignDetached(signature, message, message.size.toLong(), signingKey)) {
|
||||||
|
throw SecurityException("Couldn't sign the message with the signing key")
|
||||||
|
}
|
||||||
|
return signature
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ object SnodeAPI {
|
||||||
private const val snodeFailureThreshold = 3
|
private const val snodeFailureThreshold = 3
|
||||||
private const val useOnionRequests = true
|
private const val useOnionRequests = true
|
||||||
|
|
||||||
const val useTestnet = false
|
const val useTestnet = true
|
||||||
|
|
||||||
// Error
|
// Error
|
||||||
internal sealed class Error(val description: String) : Exception(description) {
|
internal sealed class Error(val description: String) : Exception(description) {
|
||||||
|
@ -465,7 +465,7 @@ object SnodeAPI {
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
namespace: Int,
|
namespace: Int,
|
||||||
maxSize: Int? = null,
|
maxSize: Int? = null,
|
||||||
signingKey: ByteArray,
|
signCallback: SignCallback,
|
||||||
ed25519PublicKey: Key? = null): SnodeBatchRequestInfo? {
|
ed25519PublicKey: Key? = null): SnodeBatchRequestInfo? {
|
||||||
val lastHashValue = database.getLastMessageHashValue(snode, publicKey, namespace) ?: ""
|
val lastHashValue = database.getLastMessageHashValue(snode, publicKey, namespace) ?: ""
|
||||||
val params = mutableMapOf<String, Any>(
|
val params = mutableMapOf<String, Any>(
|
||||||
|
@ -476,25 +476,10 @@ object SnodeAPI {
|
||||||
params["pubkey_ed25519"] = ed25519PublicKey.asHexString
|
params["pubkey_ed25519"] = ed25519PublicKey.asHexString
|
||||||
}
|
}
|
||||||
val timestamp = nowWithOffset
|
val timestamp = nowWithOffset
|
||||||
val signature = ByteArray(Sign.BYTES)
|
val verificationData = if (namespace == 0) "retrieve$timestamp"
|
||||||
val verificationData = if (namespace == 0) "retrieve$timestamp".toByteArray()
|
else "retrieve$namespace$timestamp"
|
||||||
else "retrieve$namespace$timestamp".toByteArray()
|
val signParameters = signCallback(verificationData, timestamp, namespace)
|
||||||
try {
|
params += signParameters
|
||||||
sodium.cryptoSignDetached(
|
|
||||||
signature,
|
|
||||||
verificationData,
|
|
||||||
verificationData.size.toLong(),
|
|
||||||
signingKey
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("BatchRetrieve", "Signing data failed with provided signing key", e)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
params["timestamp"] = timestamp
|
|
||||||
params["signature"] = Base64.encodeBytes(signature)
|
|
||||||
if (namespace != 0) {
|
|
||||||
params["namespace"] = namespace
|
|
||||||
}
|
|
||||||
if (maxSize != null) {
|
if (maxSize != null) {
|
||||||
params["max_size"] = maxSize
|
params["max_size"] = maxSize
|
||||||
}
|
}
|
||||||
|
@ -518,7 +503,7 @@ object SnodeAPI {
|
||||||
publicKey,
|
publicKey,
|
||||||
namespace,
|
namespace,
|
||||||
maxSize,
|
maxSize,
|
||||||
secretKey,
|
signingKeyCallback(secretKey),
|
||||||
ed25519PublicKey
|
ed25519PublicKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -701,23 +686,30 @@ object SnodeAPI {
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
throw Error.SigningFailed
|
throw Error.SigningFailed
|
||||||
}
|
}
|
||||||
mapOf(
|
val params = mutableMapOf<String,Any>(
|
||||||
"timestamp" to timestamp,
|
"timestamp" to timestamp,
|
||||||
"signature" to Base64.encodeBytes(signature),
|
"signature" to Base64.encodeBytes(signature),
|
||||||
"namespace" to namespace
|
|
||||||
)
|
)
|
||||||
|
if (namespace != Namespace.DEFAULT()) {
|
||||||
|
params += "namespace" to namespace
|
||||||
|
}
|
||||||
|
params
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subkeyCallback(authData: ByteArray, groupKeysConfig: GroupKeysConfig): SignCallback = { message, timestamp, namespace ->
|
fun subkeyCallback(authData: ByteArray, groupKeysConfig: GroupKeysConfig, freeAfter: Boolean = true): SignCallback = { message, timestamp, namespace ->
|
||||||
val (subaccount, subaccountSig, sig) = groupKeysConfig.subAccountSign(message.toByteArray(),authData)
|
val (subaccount, subaccountSig, sig) = groupKeysConfig.subAccountSign(message.toByteArray(),authData)
|
||||||
val params = mapOf(
|
val params = mutableMapOf(
|
||||||
"subaccount" to subaccount,
|
"subaccount" to subaccount,
|
||||||
"subaccount_sig" to subaccountSig,
|
"subaccount_sig" to subaccountSig,
|
||||||
"signature" to sig,
|
"signature" to sig,
|
||||||
"timestamp" to timestamp,
|
"timestamp" to timestamp,
|
||||||
"namespace" to namespace
|
|
||||||
)
|
)
|
||||||
groupKeysConfig.free()
|
if (namespace != Namespace.DEFAULT()) {
|
||||||
|
params += "namespace" to namespace
|
||||||
|
}
|
||||||
|
if (freeAfter) {
|
||||||
|
groupKeysConfig.free()
|
||||||
|
}
|
||||||
params
|
params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,10 @@ public class SignalServiceGroup {
|
||||||
return groupId;
|
return groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNewClosedGroup() {
|
||||||
|
return groupId.length == 33 && groupId[0] == 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
public GroupType getGroupType() { return groupType; }
|
public GroupType getGroupType() { return groupType; }
|
||||||
|
|
||||||
public Type getType() {
|
public Type getType() {
|
||||||
|
|
Loading…
Reference in New Issue