All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.net.folivo.trixnity.client.key.IncomingRoomKeyRequestEventHandler.kt Maven / Gradle / Ivy

There is a newer version: 4.7.1
Show newest version
package net.folivo.trixnity.client.key

import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import net.folivo.trixnity.client.store.AccountStore
import net.folivo.trixnity.client.store.KeyStore
import net.folivo.trixnity.client.store.OlmCryptoStore
import net.folivo.trixnity.client.store.isVerified
import net.folivo.trixnity.clientserverapi.client.MatrixClientServerApiClient
import net.folivo.trixnity.core.*
import net.folivo.trixnity.core.ClientEventEmitter.Priority
import net.folivo.trixnity.core.model.events.ClientEvent.ToDeviceEvent
import net.folivo.trixnity.core.model.events.m.ForwardedRoomKeyEventContent
import net.folivo.trixnity.core.model.events.m.KeyRequestAction
import net.folivo.trixnity.core.model.events.m.RoomKeyRequestEventContent
import net.folivo.trixnity.core.model.keys.EncryptionAlgorithm
import net.folivo.trixnity.crypto.olm.DecryptedOlmEventContainer
import net.folivo.trixnity.crypto.olm.OlmDecrypter
import net.folivo.trixnity.crypto.olm.OlmEncryptionService
import net.folivo.trixnity.olm.OlmInboundGroupSession
import net.folivo.trixnity.olm.freeAfter

private val log = KotlinLogging.logger {}

class IncomingRoomKeyRequestEventHandler(
    userInfo: UserInfo,
    private val api: MatrixClientServerApiClient,
    private val olmDecrypter: OlmDecrypter,
    private val olmEncryptionService: OlmEncryptionService,
    private val accountStore: AccountStore,
    private val keyStore: KeyStore,
    private val olmStore: OlmCryptoStore,
) : EventHandler {
    private val ownUserId = userInfo.userId

    override fun startInCoroutineScope(scope: CoroutineScope) {
        olmDecrypter.subscribe(::handleEncryptedIncomingKeyRequests).unsubscribeOnCompletion(scope)
        api.sync.subscribeEvent(subscriber = ::handleIncomingKeyRequests).unsubscribeOnCompletion(scope)
        api.sync.subscribe(Priority.AFTER_DEFAULT, ::processIncomingKeyRequests).unsubscribeOnCompletion(scope)
    }

    private val incomingRoomKeyRequests = MutableStateFlow>(setOf())

    internal fun handleEncryptedIncomingKeyRequests(event: DecryptedOlmEventContainer) {
        val content = event.decrypted.content
        if (event.decrypted.sender == ownUserId && content is RoomKeyRequestEventContent) {
            handleIncomingKeyRequests(ToDeviceEvent(content, event.decrypted.sender))
        }
    }

    internal fun handleIncomingKeyRequests(event: ToDeviceEvent) {
        if (event.sender == ownUserId) {
            log.debug { "handle incoming room key requests" }
            val content = event.content
            when (content.action) {
                KeyRequestAction.REQUEST -> incomingRoomKeyRequests.update { it + content }
                KeyRequestAction.REQUEST_CANCELLATION -> incomingRoomKeyRequests
                    .update { oldRequests -> oldRequests.filterNot { it.requestId == content.requestId }.toSet() }
            }
        }
    }

    internal suspend fun processIncomingKeyRequests() {
        incomingRoomKeyRequests.value.forEach { request ->
            val requestingDeviceId = request.requestingDeviceId
            val senderTrustLevel = keyStore.getDeviceKey(ownUserId, requestingDeviceId).first()?.trustLevel
            val requestBody = request.body
            log.debug { "process incoming room key request (requestId=${request.requestId}, isVerified=${senderTrustLevel?.isVerified})" }
            if (senderTrustLevel?.isVerified == true && requestBody != null && requestBody.algorithm == EncryptionAlgorithm.Megolm) {
                val foundInboundMegolmSession =
                    olmStore.getInboundMegolmSession(requestBody.sessionId, requestBody.roomId).first()
                if (foundInboundMegolmSession != null) {
                    log.info { "send incoming room key request answer (${request.requestId}) to device $requestingDeviceId" }
                    val encryptedAnswer =
                        olmEncryptionService.encryptOlm(
                            ForwardedRoomKeyEventContent(
                                roomId = foundInboundMegolmSession.roomId,
                                senderKey = foundInboundMegolmSession.senderKey,
                                sessionId = foundInboundMegolmSession.sessionId,
                                sessionKey = freeAfter(
                                    OlmInboundGroupSession.unpickle(
                                        checkNotNull(accountStore.getAccount()?.olmPickleKey),
                                        foundInboundMegolmSession.pickled
                                    )
                                ) { it.export(it.firstKnownIndex) },
                                senderClaimedKey = foundInboundMegolmSession.senderSigningKey,
                                forwardingKeyChain = foundInboundMegolmSession.forwardingCurve25519KeyChain,
                                algorithm = EncryptionAlgorithm.Megolm,
                            ),
                            ownUserId, requestingDeviceId
                        ).getOrNull()
                    if (encryptedAnswer != null)
                        api.user.sendToDevice(
                            mapOf(ownUserId to mapOf(requestingDeviceId to encryptedAnswer))
                        ).getOrThrow()
                } else log.info { "got a room key request (${request}), but did not found a matching room key" }
            }
            incomingRoomKeyRequests.update { it - request }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy