commit d5db0ea7f653b473fd5e1350775baab016a7b88d
parent dbafd6de210706a3fc8eb533f179db6d612c2def
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Tue, 23 Jul 2024 18:00:58 +0200
fix(e2ee): key exchange
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
Diffstat:
3 files changed, 77 insertions(+), 31 deletions(-)
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/event/EventDispatcher.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/event/EventDispatcher.kt
@@ -19,6 +19,7 @@ import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
+import me.rhunk.snapenhance.mapper.impl.CallbackMapper
import me.rhunk.snapenhance.mapper.impl.ViewBinderMapper
import java.nio.ByteBuffer
@@ -274,6 +275,34 @@ class EventDispatcher(
}
}
+ context.mappings.useMapper(CallbackMapper::class) {
+ callbacks.getClass("UploadDelegate")?.hook("uploadMedia", HookStage.BEFORE) { param ->
+ val uploadCallback = param.arg<Any>(2)
+
+ val event = context.event.post(MediaUploadEvent(
+ localMessageContent = MessageContent(param.arg(0)),
+ destinations = MessageDestinations(param.arg(1)),
+ callback = uploadCallback
+ ).apply {
+ adapter = param
+ })
+
+ if (event?.canceled == true) {
+ param.setResult(null)
+ return@hook
+ }
+
+ event?.mediaUploadCallbacks?.takeIf { it.isNotEmpty() }?.let { callbacks ->
+ Hooker.ephemeralHookObjectMethod(uploadCallback::class.java, uploadCallback, "onUploadFinished", HookStage.BEFORE) { methodParam ->
+ val messageContent = MessageContent(methodParam.arg(1))
+ callbacks.forEach {
+ it(MediaUploadEvent.MediaUploadResult(messageContent))
+ }
+ }
+ }
+ }
+ }
+
hookViewBinder()
}
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/event/events/impl/MediaUploadEvent.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/event/events/impl/MediaUploadEvent.kt
@@ -0,0 +1,21 @@
+package me.rhunk.snapenhance.core.event.events.impl
+
+import me.rhunk.snapenhance.core.event.events.AbstractHookEvent
+import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
+import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
+
+class MediaUploadEvent(
+ val localMessageContent: MessageContent,
+ val destinations: MessageDestinations,
+ val callback: Any,
+): AbstractHookEvent() {
+ class MediaUploadResult(
+ val messageContent: MessageContent
+ )
+
+ val mediaUploadCallbacks = mutableListOf<(MediaUploadResult) -> Unit>()
+
+ fun onMediaUploaded(callback: (MediaUploadResult) -> Unit) {
+ mediaUploadCallbacks.add(callback)
+ }
+}+
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/EndToEndEncryption.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/EndToEndEncryption.kt
@@ -7,11 +7,8 @@ import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.Shape
import android.view.View
import android.view.ViewGroup
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
+import android.widget.LinearLayout
+import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
@@ -29,18 +26,15 @@ import me.rhunk.snapenhance.common.util.lazyBridge
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.common.util.protobuf.ProtoWriter
-import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent
-import me.rhunk.snapenhance.core.event.events.impl.BuildMessageEvent
-import me.rhunk.snapenhance.core.event.events.impl.NativeUnaryCallEvent
-import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent
+import me.rhunk.snapenhance.core.event.events.impl.*
import me.rhunk.snapenhance.core.features.MessagingRuleFeature
import me.rhunk.snapenhance.core.features.impl.ui.ConversationToolbox
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.core.ui.addForegroundDrawable
+import me.rhunk.snapenhance.core.ui.findParent
import me.rhunk.snapenhance.core.ui.removeForegroundDrawable
import me.rhunk.snapenhance.core.util.EvictingMap
import me.rhunk.snapenhance.core.util.hook.HookStage
-import me.rhunk.snapenhance.core.util.hook.Hooker
import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
@@ -250,7 +244,9 @@ class EndToEndEncryption : MessagingRuleFeature(
context.event.subscribe(BindViewEvent::class) { event ->
event.chatMessage { conversationId, messageId ->
- val viewGroup = event.view.parent as? ViewGroup ?: return@subscribe
+ val viewGroup = event.view.findParent(maxIteration = 3) {
+ it is LinearLayout
+ } as? ViewGroup ?: event.view.parent as? ViewGroup ?: return@chatMessage
viewGroup.findViewWithTag<View>(specialCard)?.also {
viewGroup.removeView(it)
@@ -312,28 +308,23 @@ class EndToEndEncryption : MessagingRuleFeature(
defer {
val forceMessageEncryption by context.config.experimental.e2eEncryption.forceMessageEncryption
- context.mappings.useMapper(CallbackMapper::class) {
- callbacks.getClass("UploadDelegate")?.hook("uploadMedia", HookStage.BEFORE) { param ->
- val messageDestinations = MessageDestinations(param.arg(1))
- val uploadCallback = param.arg<Any>(2)
- val e2eeConversations = messageDestinations.getEndToEndConversations()
- if (e2eeConversations.isEmpty()) return@hook
+ context.event.subscribe(MediaUploadEvent::class) { event ->
+ val e2eeConversations = event.destinations.getEndToEndConversations()
+ if (e2eeConversations.isEmpty()) return@subscribe
- if (messageDestinations.conversations!!.size != e2eeConversations.size || messageDestinations.stories?.isNotEmpty() == true) {
- context.log.debug("skipping encryption")
- return@hook
- }
+ if (event.destinations.conversations!!.size != e2eeConversations.size || event.destinations.stories?.isNotEmpty() == true) {
+ context.log.debug("skipping encryption")
+ return@subscribe
+ }
- Hooker.hookObjectMethod(uploadCallback::class.java, uploadCallback, "onUploadFinished", HookStage.BEFORE) { methodParam ->
- val messageContent = MessageContent(methodParam.arg(1))
- runCatching {
- messageContent.content = ProtoWriter().apply {
- writeEncryptedMessage(e2eeConversations.map { getE2EParticipants(it) }.flatten().distinct(), messageContent.content!!)
- }.toByteArray()
- }.onFailure {
- context.log.error("Failed to encrypt message", it)
- context.longToast(translation["encryption_failed_toast"])
- }
+ event.onMediaUploaded { result ->
+ runCatching {
+ result.messageContent.content = ProtoWriter().apply {
+ writeEncryptedMessage(e2eeConversations.map { getE2EParticipants(it) }.flatten().distinct(), result.messageContent.content!!)
+ }.toByteArray()
+ }.onFailure {
+ context.log.error("Failed to encrypt message", it)
+ context.longToast(translation["encryption_failed_toast"])
}
}
}
@@ -359,6 +350,10 @@ class EndToEndEncryption : MessagingRuleFeature(
}
event.addInvokeLater {
+ // check if the content is already encrypted
+ if (ProtoReader(messageContent.content!!).getByteArray(2, 1, 2) != null) {
+ return@addInvokeLater
+ }
if (event.messageContent.localMediaReferences?.isEmpty() == true) {
runCatching {
event.messageContent.content = ProtoWriter().apply {