share keys for history take2
This commit is contained in:
parent
93aac8faea
commit
9b8e45ebfe
@ -81,7 +81,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = testHelper.runBlockingTest {
|
||||
aliceSession.createRoom(CreateRoomParams().apply {
|
||||
aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = roomHistoryVisibility
|
||||
name = "MyRoom"
|
||||
})
|
||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
|
||||
import android.util.Log
|
||||
import androidx.test.filters.LargeTest
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.amshove.kluent.internal.assertNotEquals
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
@ -101,7 +102,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
// Bob should be able to decrypt the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
@ -119,7 +120,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
// Alice invites new user to the room
|
||||
testHelper.runBlockingTest {
|
||||
Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}")
|
||||
aliceRoomPOV.invite(arisSession.myUserId)
|
||||
aliceRoomPOV.membershipService().invite(arisSession.myUserId)
|
||||
}
|
||||
|
||||
waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper)
|
||||
@ -135,7 +136,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
// Aris should be able to decrypt the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
@ -152,7 +153,9 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
// Aris should not even be able to get the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(aliceMessageId!!)
|
||||
timelineEvent == null
|
||||
}
|
||||
}
|
||||
@ -242,7 +245,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
// Alice
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
|
||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
// val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
|
||||
// Bob
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
@ -256,35 +259,62 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
Log.v("#E2E TEST ROTATION", "Alice sent message to roomId: $e2eRoomID")
|
||||
|
||||
// Bob should be able to decrypt the message
|
||||
var firstAliceMessageMegolmSessionId: String? = null
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rotation has already been done so we do not need to rotate again
|
||||
assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false)
|
||||
Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
|
||||
|
||||
var secondAliceMessageSessionId: String? = null
|
||||
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(secondMessage)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals("No rotation needed session should be the same", firstAliceMessageMegolmSessionId, secondAliceMessageSessionId)
|
||||
Log.v("#E2E TEST ROTATION", "No rotation needed yet")
|
||||
|
||||
// Let's change the room history visibility
|
||||
testHelper.waitWithLatch {
|
||||
aliceRoomPOV.sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
body = RoomHistoryVisibilityContent(_historyVisibility = nextRoomHistoryVisibility._historyVisibility).toContent()
|
||||
)
|
||||
aliceRoomPOV.stateService()
|
||||
.sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
body = RoomHistoryVisibilityContent(
|
||||
_historyVisibility = nextRoomHistoryVisibility._historyVisibility
|
||||
).toContent()
|
||||
)
|
||||
it.countDown()
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
|
||||
.stateService()
|
||||
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)
|
||||
?.content
|
||||
?.toModel<RoomHistoryVisibilityContent>()
|
||||
@ -293,13 +323,31 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
var aliceThirdMessageSessionId: String? = null
|
||||
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(thirdMessage)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> {
|
||||
assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false)
|
||||
assertEquals("Session shouldn't have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId)
|
||||
Log.v("#E2E TEST ROTATION", "Rotation is not needed")
|
||||
}
|
||||
initRoomHistoryVisibility.shouldShareHistory() != nextRoomHistoryVisibility.historyVisibility!!.shouldShareHistory() -> {
|
||||
assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), true)
|
||||
assertNotEquals("Session should have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId)
|
||||
Log.v("#E2E TEST ROTATION", "Rotation is needed!")
|
||||
}
|
||||
}
|
||||
@ -308,10 +356,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? {
|
||||
aliceRoomPOV.sendTextMessage(text)
|
||||
aliceRoomPOV.sendService().sendTextMessage(text)
|
||||
var sentEventId: String? = null
|
||||
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
|
||||
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
|
||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||
timeline.start()
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val decryptedMsg = timeline.getSnapshot()
|
||||
|
@ -72,7 +72,7 @@ class PreShareKeysTest : InstrumentedTest {
|
||||
assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
|
||||
assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)
|
||||
|
||||
val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier()
|
||||
val megolmSessionId = bobInboundForAlice.session.sessionIdentifier()
|
||||
|
||||
assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId)
|
||||
|
||||
|
@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestData
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
|
||||
/**
|
||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||
*/
|
||||
internal data class KeysBackupScenarioData(
|
||||
val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
|
||||
val aliceKeys: List<MXInboundMegolmSessionWrapper>,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session
|
||||
) {
|
||||
|
@ -301,7 +301,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||
|
||||
// - Check encryptGroupSession() returns stg
|
||||
val keyBackupData = keysBackup.encryptGroupSession(session)
|
||||
val keyBackupData = testHelper.runBlockingTest { keysBackup.encryptGroupSession(session) }
|
||||
assertNotNull(keyBackupData)
|
||||
assertNotNull(keyBackupData!!.sessionData)
|
||||
|
||||
@ -312,7 +312,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val sessionData = keysBackup
|
||||
.decryptKeyBackupData(
|
||||
keyBackupData,
|
||||
session.olmInboundGroupSession!!.sessionIdentifier(),
|
||||
session.safeSessionId!!,
|
||||
cryptoTestData.roomId,
|
||||
decryption!!
|
||||
)
|
||||
|
@ -187,7 +187,7 @@ internal class KeysBackupTestHelper(
|
||||
// - Alice must have the same keys on both devices
|
||||
for (aliceKey1 in testData.aliceKeys) {
|
||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
.getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
|
||||
.getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
|
||||
Assert.assertNotNull(aliceKey2)
|
||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
}
|
||||
|
@ -181,5 +181,5 @@ interface CryptoService {
|
||||
/**
|
||||
* Share all inbound sessions of the last chunk messages to the provided userId devices
|
||||
*/
|
||||
fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?)
|
||||
suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?)
|
||||
}
|
||||
|
@ -69,5 +69,12 @@ data class ForwardedRoomKeyContent(
|
||||
* private part of this key unless they have done device verification.
|
||||
*/
|
||||
@Json(name = "sender_claimed_ed25519_key")
|
||||
val senderClaimedEd25519Key: String? = null
|
||||
val senderClaimedEd25519Key: String? = null,
|
||||
|
||||
/**
|
||||
* MSC3061
|
||||
* Identifies keys that were sent when the room's visibility setting was set to world_readable or shared
|
||||
*/
|
||||
@Json(name = "org.matrix.msc3061.shared_history")
|
||||
val sharedHistory: Boolean? = false,
|
||||
)
|
||||
|
@ -38,5 +38,13 @@ data class RoomKeyContent(
|
||||
|
||||
// should be a Long but it is sometimes a double
|
||||
@Json(name = "chain_index")
|
||||
val chainIndex: Any? = null
|
||||
val chainIndex: Any? = null,
|
||||
|
||||
/**
|
||||
* MSC3061
|
||||
* Identifies keys that were sent when the room's visibility setting was set to world_readable or shared
|
||||
*/
|
||||
@Json(name = "org.matrix.msc3061.shared_history")
|
||||
val sharedHistory: Boolean? = false
|
||||
|
||||
)
|
||||
|
@ -110,6 +110,7 @@ import org.matrix.olm.OlmManager
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
@ -965,10 +966,13 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
|
||||
if (!event.isStateEvent()) return
|
||||
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
|
||||
eventContent?.historyVisibility?.let {
|
||||
cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED)
|
||||
cryptoStore.setShouldShareHistory(roomId, it.shouldShareHistory())
|
||||
} ?: cryptoStore.setShouldShareHistory(roomId, false)
|
||||
val historyVisibility = eventContent?.historyVisibility
|
||||
if (historyVisibility == null) {
|
||||
cryptoStore.setShouldShareHistory(roomId, false)
|
||||
} else {
|
||||
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
|
||||
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1338,36 +1342,26 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching {
|
||||
deviceListManager.downloadKeys(listOf(userId), false)
|
||||
}.mapCatching {
|
||||
val userDevices = cryptoStore.getUserDevices(userId)
|
||||
userDevices?.forEach {
|
||||
// Lets share the provided inbound sessions for every user device
|
||||
val deviceId = it.key
|
||||
sessionInfoSet?.mapNotNull { sessionInfo ->
|
||||
// Get inbound session from sessionId and sessionKey
|
||||
cryptoStore.getInboundGroupSession(
|
||||
sessionId = sessionInfo.sessionId,
|
||||
senderKey = sessionInfo.senderKey,
|
||||
sharedHistory = true
|
||||
)
|
||||
}?.filter { inboundGroupSession ->
|
||||
// Prevent injecting a forged encrypted message and using session_id/sender_key of another room.
|
||||
(inboundGroupSession.roomId == roomId).also {
|
||||
Timber.tag(loggerTag.value).d("Forged encrypted message detected for roomId:$roomId")
|
||||
}
|
||||
}?.forEach { inboundGroupSession ->
|
||||
// Share the sharable session to userId with deviceId
|
||||
val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true)
|
||||
val algorithm = exportedKeys?.algorithm
|
||||
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm)
|
||||
decryptor?.shareForwardKeysWithDevice(exportedKeys, deviceId, userId)
|
||||
Timber.i("## CRYPTO | Sharing inbound session")
|
||||
}
|
||||
}
|
||||
override suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) {
|
||||
deviceListManager.downloadKeys(listOf(userId), false)
|
||||
val userDevices = cryptoStore.getUserDeviceList(userId)
|
||||
val sessionToShare = sessionInfoSet.orEmpty().mapNotNull { sessionInfo ->
|
||||
// Get inbound session from sessionId and sessionKey
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
olmDevice.getInboundGroupSession(
|
||||
sessionId = sessionInfo.sessionId,
|
||||
senderKey = sessionInfo.senderKey,
|
||||
roomId = roomId
|
||||
).takeIf { it.wrapper.sessionData.sharedHistory }
|
||||
}
|
||||
}
|
||||
|
||||
userDevices?.forEach { deviceInfo ->
|
||||
// Lets share the provided inbound sessions for every user device
|
||||
sessionToShare.forEach { inboundGroupSession ->
|
||||
val encryptor = roomEncryptorsStore.get(roomId)
|
||||
encryptor?.shareHistoryKeysWithDevice(inboundGroupSession, deviceInfo)
|
||||
Timber.i("## CRYPTO | Sharing inbound session")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import timber.log.Timber
|
||||
import java.util.Timer
|
||||
@ -31,7 +31,7 @@ import java.util.TimerTask
|
||||
import javax.inject.Inject
|
||||
|
||||
internal data class InboundGroupSessionHolder(
|
||||
val wrapper: OlmInboundGroupSessionWrapper2,
|
||||
val wrapper: MXInboundMegolmSessionWrapper,
|
||||
val mutex: Mutex = Mutex()
|
||||
)
|
||||
|
||||
@ -58,7 +58,7 @@ internal class InboundGroupSessionStore @Inject constructor(
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}")
|
||||
store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper })
|
||||
oldValue.wrapper.olmInboundGroupSession?.releaseSession()
|
||||
oldValue.wrapper.session.releaseSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,7 +67,7 @@ internal class InboundGroupSessionStore @Inject constructor(
|
||||
private val timer = Timer()
|
||||
private var timerTask: TimerTask? = null
|
||||
|
||||
private val dirtySession = mutableListOf<OlmInboundGroupSessionWrapper2>()
|
||||
private val dirtySession = mutableListOf<InboundGroupSessionHolder>()
|
||||
|
||||
@Synchronized
|
||||
fun clear() {
|
||||
@ -90,12 +90,12 @@ internal class InboundGroupSessionStore @Inject constructor(
|
||||
@Synchronized
|
||||
fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
|
||||
Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
|
||||
dirtySession.remove(old.wrapper)
|
||||
dirtySession.remove(old)
|
||||
store.removeInboundGroupSession(sessionId, senderKey)
|
||||
sessionCache.remove(CacheKey(sessionId, senderKey))
|
||||
|
||||
// release removed session
|
||||
old.wrapper.olmInboundGroupSession?.releaseSession()
|
||||
old.wrapper.session.releaseSession()
|
||||
|
||||
internalStoreGroupSession(new, sessionId, senderKey)
|
||||
}
|
||||
@ -108,7 +108,7 @@ internal class InboundGroupSessionStore @Inject constructor(
|
||||
private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
|
||||
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}")
|
||||
// We want to batch this a bit for performances
|
||||
dirtySession.add(holder.wrapper)
|
||||
dirtySession.add(holder)
|
||||
|
||||
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
|
||||
// first time seen, put it in memory cache while waiting for batch insert
|
||||
@ -127,12 +127,12 @@ internal class InboundGroupSessionStore @Inject constructor(
|
||||
|
||||
@Synchronized
|
||||
private fun batchSave() {
|
||||
val toSave = mutableListOf<OlmInboundGroupSessionWrapper2>().apply { addAll(dirtySession) }
|
||||
val toSave = mutableListOf<InboundGroupSessionHolder>().apply { addAll(dirtySession) }
|
||||
dirtySession.clear()
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}")
|
||||
tryOrNull {
|
||||
store.storeInboundGroupSessions(toSave)
|
||||
store.storeInboundGroupSessions(toSave.map { it.wrapper })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
|
||||
}
|
||||
|
||||
val export = sessionHolder.mutex.withLock {
|
||||
sessionHolder.wrapper.exportKeys(/**TODO*/ false ,chainIndex)
|
||||
sessionHolder.wrapper.exportKeys(chainIndex)
|
||||
} ?: return false.also {
|
||||
Timber.tag(loggerTag.value)
|
||||
.e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}")
|
||||
|
@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
@ -38,6 +39,7 @@ import org.matrix.android.sdk.internal.util.convertToUTF8
|
||||
import org.matrix.android.sdk.internal.util.time.Clock
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmException
|
||||
import org.matrix.olm.OlmInboundGroupSession
|
||||
import org.matrix.olm.OlmMessage
|
||||
import org.matrix.olm.OlmOutboundGroupSession
|
||||
import org.matrix.olm.OlmSession
|
||||
@ -514,8 +516,9 @@ internal class MXOlmDevice @Inject constructor(
|
||||
return MXOutboundSessionInfo(
|
||||
sessionId = sessionId,
|
||||
sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
|
||||
clock,
|
||||
restoredOutboundGroupSession.creationTime
|
||||
clock = clock,
|
||||
creationTime = restoredOutboundGroupSession.creationTime,
|
||||
sharedHistory = restoredOutboundGroupSession.sharedHistory
|
||||
)
|
||||
}
|
||||
return null
|
||||
@ -600,38 +603,44 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @param exportFormat true if the megolm keys are in export format
|
||||
* @return true if the operation succeeds.
|
||||
*/
|
||||
fun addInboundGroupSession(
|
||||
sessionId: String,
|
||||
sessionKey: String,
|
||||
roomId: String,
|
||||
senderKey: String,
|
||||
forwardingCurve25519KeyChain: List<String>,
|
||||
keysClaimed: Map<String, String>,
|
||||
exportFormat: Boolean
|
||||
): AddSessionResult {
|
||||
val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
|
||||
fun addInboundGroupSession(sessionId: String,
|
||||
sessionKey: String,
|
||||
roomId: String,
|
||||
senderKey: String,
|
||||
forwardingCurve25519KeyChain: List<String>,
|
||||
keysClaimed: Map<String, String>,
|
||||
exportFormat: Boolean,
|
||||
sharedHistory: Boolean): AddSessionResult {
|
||||
val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") {
|
||||
if (exportFormat) {
|
||||
OlmInboundGroupSession.importSession(sessionKey)
|
||||
} else {
|
||||
OlmInboundGroupSession(sessionKey)
|
||||
}
|
||||
}
|
||||
|
||||
val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||
val existingSession = existingSessionHolder?.wrapper
|
||||
// If we have an existing one we should check if the new one is not better
|
||||
if (existingSession != null) {
|
||||
Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
|
||||
try {
|
||||
val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also {
|
||||
val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex } ?: return AddSessionResult.NotImported.also {
|
||||
// This is quite unexpected, could throw if native was released?
|
||||
Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
|
||||
candidateSession.olmInboundGroupSession?.releaseSession()
|
||||
candidateSession?.releaseSession()
|
||||
// Probably should discard it?
|
||||
}
|
||||
val newKnownFirstIndex = candidateSession.firstKnownIndex
|
||||
val newKnownFirstIndex = tryOrNull("Failed to get candidate first known index") { candidateSession?.firstKnownIndex }
|
||||
// If our existing session is better we keep it
|
||||
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
|
||||
Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
|
||||
candidateSession.olmInboundGroupSession?.releaseSession()
|
||||
candidateSession?.releaseSession()
|
||||
return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
|
||||
candidateSession.olmInboundGroupSession?.releaseSession()
|
||||
candidateSession?.releaseSession()
|
||||
return AddSessionResult.NotImported
|
||||
}
|
||||
}
|
||||
@ -639,36 +648,42 @@ internal class MXOlmDevice @Inject constructor(
|
||||
Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId")
|
||||
|
||||
// sanity check on the new session
|
||||
val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
|
||||
if (null == candidateOlmInboundSession) {
|
||||
if (null == candidateSession) {
|
||||
Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session <null>")
|
||||
return AddSessionResult.NotImported
|
||||
}
|
||||
|
||||
try {
|
||||
if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
|
||||
if (candidateSession.sessionIdentifier() != sessionId) {
|
||||
Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
|
||||
candidateOlmInboundSession.releaseSession()
|
||||
candidateSession.releaseSession()
|
||||
return AddSessionResult.NotImported
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
candidateOlmInboundSession.releaseSession()
|
||||
candidateSession.releaseSession()
|
||||
Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
|
||||
return AddSessionResult.NotImported
|
||||
}
|
||||
|
||||
candidateSession.senderKey = senderKey
|
||||
candidateSession.roomId = roomId
|
||||
candidateSession.keysClaimed = keysClaimed
|
||||
candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
|
||||
val candidateSessionData = InboundGroupSessionData(
|
||||
senderKey = senderKey,
|
||||
roomId = roomId,
|
||||
keysClaimed = keysClaimed,
|
||||
forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
|
||||
sharedHistory = sharedHistory,
|
||||
)
|
||||
|
||||
val wrapper = MXInboundMegolmSessionWrapper(
|
||||
candidateSession,
|
||||
candidateSessionData
|
||||
)
|
||||
if (existingSession != null) {
|
||||
inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
|
||||
inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(wrapper), sessionId, senderKey)
|
||||
} else {
|
||||
inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
|
||||
inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(wrapper), sessionId, senderKey)
|
||||
}
|
||||
|
||||
return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0)
|
||||
return AddSessionResult.Imported(candidateSession.firstKnownIndex.toInt())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -677,41 +692,22 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @param megolmSessionsData the megolm sessions data
|
||||
* @return the successfully imported sessions.
|
||||
*/
|
||||
fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<OlmInboundGroupSessionWrapper2> {
|
||||
val sessions = ArrayList<OlmInboundGroupSessionWrapper2>(megolmSessionsData.size)
|
||||
fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<MXInboundMegolmSessionWrapper> {
|
||||
val sessions = ArrayList<MXInboundMegolmSessionWrapper>(megolmSessionsData.size)
|
||||
|
||||
for (megolmSessionData in megolmSessionsData) {
|
||||
val sessionId = megolmSessionData.sessionId ?: continue
|
||||
val senderKey = megolmSessionData.senderKey ?: continue
|
||||
val roomId = megolmSessionData.roomId
|
||||
|
||||
var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null
|
||||
|
||||
try {
|
||||
candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (candidateSessionToImport?.olmInboundGroupSession == null) {
|
||||
Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session")
|
||||
continue
|
||||
}
|
||||
|
||||
val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession
|
||||
try {
|
||||
if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) {
|
||||
Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
|
||||
candidateOlmInboundGroupSession?.releaseSession()
|
||||
continue
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed")
|
||||
candidateOlmInboundGroupSession?.releaseSession()
|
||||
val candidateSessionToImport = try {
|
||||
MXInboundMegolmSessionWrapper.newFromMegolmData(megolmSessionData, true)
|
||||
} catch (e: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Failed to import session $senderKey/$sessionId")
|
||||
continue
|
||||
}
|
||||
|
||||
val candidateOlmInboundGroupSession = candidateSessionToImport.session
|
||||
val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||
val existingSession = existingSessionHolder?.wrapper
|
||||
|
||||
@ -721,16 +717,16 @@ internal class MXOlmDevice @Inject constructor(
|
||||
sessions.add(candidateSessionToImport)
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex }
|
||||
val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex }
|
||||
val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex }
|
||||
val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.session.firstKnownIndex }
|
||||
|
||||
if (existingFirstKnown == null || candidateFirstKnownIndex == null) {
|
||||
// should not happen?
|
||||
candidateSessionToImport.olmInboundGroupSession?.releaseSession()
|
||||
candidateSessionToImport.session.releaseSession()
|
||||
Timber.tag(loggerTag.value)
|
||||
.w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex")
|
||||
} else {
|
||||
if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) {
|
||||
if (existingFirstKnown <= candidateFirstKnownIndex) {
|
||||
// Ignore this, keep existing
|
||||
candidateOlmInboundGroupSession.releaseSession()
|
||||
} else {
|
||||
@ -774,18 +770,17 @@ internal class MXOlmDevice @Inject constructor(
|
||||
): OlmDecryptionResult {
|
||||
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
val wrapper = sessionHolder.wrapper
|
||||
val inboundGroupSession = wrapper.olmInboundGroupSession
|
||||
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
|
||||
if (roomId != wrapper.roomId) {
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
|
||||
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
|
||||
}
|
||||
val decryptResult = try {
|
||||
sessionHolder.mutex.withLock {
|
||||
inboundGroupSession.decryptMessage(body)
|
||||
val inboundGroupSession = wrapper.session
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (roomId == wrapper.roomId) {
|
||||
val decryptResult = try {
|
||||
sessionHolder.mutex.withLock {
|
||||
inboundGroupSession.decryptMessage(body)
|
||||
}
|
||||
} catch (e: OlmException) {
|
||||
Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||
throw MXCryptoError.OlmError(e)
|
||||
}
|
||||
} catch (e: OlmException) {
|
||||
Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||
@ -820,12 +815,27 @@ internal class MXOlmDevice @Inject constructor(
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
|
||||
}
|
||||
|
||||
return OlmDecryptionResult(
|
||||
payload,
|
||||
wrapper.keysClaimed,
|
||||
senderKey,
|
||||
wrapper.forwardingCurve25519KeyChain
|
||||
)
|
||||
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
|
||||
val payload = try {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
||||
adapter.fromJson(payloadString)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
|
||||
}
|
||||
|
||||
return OlmDecryptionResult(
|
||||
payload,
|
||||
wrapper.sessionData.keysClaimed,
|
||||
senderKey,
|
||||
wrapper.sessionData.forwardingCurve25519KeyChain
|
||||
)
|
||||
} else {
|
||||
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
|
||||
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -437,7 +437,10 @@ internal class OutgoingKeyRequestManager @Inject constructor(
|
||||
if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) {
|
||||
// let's see what's the index
|
||||
val knownIndex = tryOrNull {
|
||||
inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex
|
||||
inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")
|
||||
?.wrapper
|
||||
?.session
|
||||
?.firstKnownIndex
|
||||
}
|
||||
if (knownIndex != null && knownIndex <= request.fromIndex) {
|
||||
// we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request
|
||||
|
@ -84,8 +84,9 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
||||
megolmSessionData.senderKey ?: "",
|
||||
tryOrNull {
|
||||
olmInboundGroupSessionWrappers
|
||||
.firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId }
|
||||
?.firstKnownIndex?.toInt()
|
||||
.firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId }
|
||||
?.session?.firstKnownIndex
|
||||
?.toInt()
|
||||
} ?: 0
|
||||
)
|
||||
|
||||
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.algorithms
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||
|
||||
/**
|
||||
@ -44,6 +43,4 @@ internal interface IMXDecrypting {
|
||||
* @param defaultKeysBackupService the keys backup service
|
||||
*/
|
||||
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
|
||||
|
||||
fun shareForwardKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) {}
|
||||
}
|
||||
|
@ -16,7 +16,9 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.algorithms
|
||||
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
|
||||
|
||||
/**
|
||||
* An interface for encrypting data.
|
||||
@ -32,4 +34,6 @@ internal interface IMXEncrypting {
|
||||
* @return the encrypted content
|
||||
*/
|
||||
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
|
||||
|
||||
suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
|
||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||
@ -241,13 +240,14 @@ internal class MXMegolmDecryption(
|
||||
|
||||
Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
|
||||
val addSessionResult = olmDevice.addInboundGroupSession(
|
||||
roomKeyContent.sessionId,
|
||||
roomKeyContent.sessionKey,
|
||||
roomKeyContent.roomId,
|
||||
senderKey,
|
||||
forwardingCurve25519KeyChain,
|
||||
keysClaimed,
|
||||
exportFormat
|
||||
sessionId = roomKeyContent.sessionId,
|
||||
sessionKey = roomKeyContent.sessionKey,
|
||||
roomId = roomKeyContent.roomId,
|
||||
senderKey = senderKey,
|
||||
forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
|
||||
keysClaimed = keysClaimed,
|
||||
exportFormat = exportFormat,
|
||||
sharedHistory = roomKeyContent.sharedHistory ?: false
|
||||
)
|
||||
|
||||
when (addSessionResult) {
|
||||
@ -309,43 +309,4 @@ internal class MXMegolmDecryption(
|
||||
newSessionListener?.onNewSession(roomId, senderKey, sessionId)
|
||||
}
|
||||
|
||||
override fun shareForwardKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) {
|
||||
// exportedKeys ?: return
|
||||
// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
// runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||
// .mapCatching {
|
||||
// val deviceInfo = cryptoStore.getUserDevice(userId, deviceId)
|
||||
// if (deviceInfo == null) {
|
||||
// throw RuntimeException()
|
||||
// } else {
|
||||
// val devicesByUser = mapOf(userId to listOf(deviceInfo))
|
||||
// val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
// val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
|
||||
// if (olmSessionResult?.sessionId == null) {
|
||||
// // no session with this device, probably because there
|
||||
// // were no one-time keys.
|
||||
// Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.")
|
||||
// return@mapCatching
|
||||
// }
|
||||
// Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${exportedKeys.sessionId} with device $userId:$deviceId")
|
||||
//
|
||||
// val payloadJson = mapOf(
|
||||
// "type" to EventType.FORWARDED_ROOM_KEY,
|
||||
// "content" to exportedKeys
|
||||
// )
|
||||
//
|
||||
// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||
// val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
// sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||
// Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${exportedKeys.sessionId} to $userId:$deviceId")
|
||||
// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
// try {
|
||||
// sendToDeviceTask.execute(sendToDeviceParams)
|
||||
// } catch (failure: Throwable) {
|
||||
// Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${exportedKeys.sessionId} to $userId:$deviceId")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
|
||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||
@ -151,14 +152,27 @@ internal class MXMegolmEncryption(
|
||||
"ed25519" to olmDevice.deviceEd25519Key!!
|
||||
)
|
||||
|
||||
val sharedHistory = cryptoStore.shouldShareHistory(roomId)
|
||||
Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() as sharedHistory $sharedHistory")
|
||||
olmDevice.addInboundGroupSession(
|
||||
sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
|
||||
emptyList(), keysClaimedMap, false
|
||||
sessionId = sessionId!!,
|
||||
sessionKey = olmDevice.getSessionKey(sessionId)!!,
|
||||
roomId = roomId,
|
||||
senderKey = olmDevice.deviceCurve25519Key!!,
|
||||
forwardingCurve25519KeyChain = emptyList(),
|
||||
keysClaimed = keysClaimedMap,
|
||||
exportFormat = false,
|
||||
sharedHistory = sharedHistory
|
||||
)
|
||||
|
||||
defaultKeysBackupService.maybeBackupKeys()
|
||||
|
||||
return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock)
|
||||
return MXOutboundSessionInfo(
|
||||
sessionId = sessionId,
|
||||
sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore),
|
||||
clock = clock,
|
||||
sharedHistory = sharedHistory
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -173,11 +187,7 @@ internal class MXMegolmEncryption(
|
||||
// Need to make a brand new session?
|
||||
session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) ||
|
||||
// Is there a room history visibility change since the last outboundSession
|
||||
cryptoStore.needsRotationDueToVisibilityChange(roomId).also {
|
||||
if (it) {
|
||||
Timber.tag(loggerTag.value).d("roomId:$roomId Room history visibility change detected since the last outbound session")
|
||||
}
|
||||
} ||
|
||||
cryptoStore.shouldShareHistory(roomId) != session.sharedHistory ||
|
||||
// Determine if we have shared with anyone we shouldn't have
|
||||
session.sharedWithTooManyDevices(devicesInRoom)) {
|
||||
Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.")
|
||||
@ -240,23 +250,24 @@ internal class MXMegolmEncryption(
|
||||
* @param session the session info
|
||||
* @param devicesByUser the devices map
|
||||
*/
|
||||
private suspend fun shareUserDevicesKey(
|
||||
session: MXOutboundSessionInfo,
|
||||
devicesByUser: Map<String, List<CryptoDeviceInfo>>
|
||||
) {
|
||||
val sessionKey = olmDevice.getSessionKey(session.sessionId)
|
||||
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
|
||||
private suspend fun shareUserDevicesKey(sessionInfo: MXOutboundSessionInfo,
|
||||
devicesByUser: Map<String, List<CryptoDeviceInfo>>) {
|
||||
val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also {
|
||||
Timber.tag(loggerTag.value).v("shareUserDevicesKey() Failed to share session, failed to export")
|
||||
}
|
||||
val chainIndex = olmDevice.getMessageIndex(sessionInfo.sessionId)
|
||||
|
||||
val submap = HashMap<String, Any>()
|
||||
submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
|
||||
submap["room_id"] = roomId
|
||||
submap["session_id"] = session.sessionId
|
||||
submap["session_key"] = sessionKey!!
|
||||
submap["chain_index"] = chainIndex
|
||||
|
||||
val payload = HashMap<String, Any>()
|
||||
payload["type"] = EventType.ROOM_KEY
|
||||
payload["content"] = submap
|
||||
val payload = mapOf(
|
||||
"type" to EventType.ROOM_KEY,
|
||||
"content" to mapOf(
|
||||
"algorithm" to MXCRYPTO_ALGORITHM_MEGOLM,
|
||||
"room_id" to roomId,
|
||||
"session_id" to sessionInfo.sessionId,
|
||||
"session_key" to sessionKey,
|
||||
"chain_index" to chainIndex,
|
||||
"org.matrix.msc3061.shared_history" to sessionInfo.sharedHistory
|
||||
)
|
||||
)
|
||||
|
||||
var t0 = clock.epochMillis()
|
||||
Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
|
||||
@ -298,7 +309,7 @@ internal class MXMegolmEncryption(
|
||||
// for dead devices on every message.
|
||||
for ((_, devicesToShareWith) in devicesByUser) {
|
||||
for (deviceInfo in devicesToShareWith) {
|
||||
session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex)
|
||||
sessionInfo.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex)
|
||||
// XXX is it needed to add it to the audit trail?
|
||||
// For now decided that no, we are more interested by forward trail
|
||||
}
|
||||
@ -306,8 +317,8 @@ internal class MXMegolmEncryption(
|
||||
|
||||
if (haveTargets) {
|
||||
t0 = clock.epochMillis()
|
||||
Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target")
|
||||
Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
|
||||
Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${sessionInfo.sessionId} : has target")
|
||||
Timber.tag(loggerTag.value).d("sending to device room key for ${sessionInfo.sessionId} to ${contentMap.toDebugString()}")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
|
||||
try {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
@ -316,7 +327,7 @@ internal class MXMegolmEncryption(
|
||||
Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms")
|
||||
} catch (failure: Throwable) {
|
||||
// What to do here...
|
||||
Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>")
|
||||
Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${sessionInfo.sessionId}>")
|
||||
}
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key")
|
||||
@ -326,7 +337,7 @@ internal class MXMegolmEncryption(
|
||||
// XXX offload?, as they won't read the message anyhow?
|
||||
notifyKeyWithHeld(
|
||||
noOlmToNotify,
|
||||
session.sessionId,
|
||||
sessionInfo.sessionId,
|
||||
olmDevice.deviceCurve25519Key,
|
||||
WithHeldCode.NO_OLM
|
||||
)
|
||||
@ -520,6 +531,51 @@ internal class MXMegolmEncryption(
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {
|
||||
if (!inboundSessionWrapper.wrapper.sessionData.sharedHistory) throw IllegalArgumentException("This key can't be shared")
|
||||
Timber.tag(loggerTag.value).i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
|
||||
val userId = deviceInfo.userId
|
||||
val deviceId = deviceInfo.deviceId
|
||||
val devicesByUser = mapOf(userId to listOf(deviceInfo))
|
||||
val usersDeviceMap = try {
|
||||
ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).i(failure, "process shareHistoryKeys failed to ensure olm")
|
||||
// process anyway?
|
||||
null
|
||||
}
|
||||
val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId)
|
||||
if (olmSessionResult?.sessionId == null) {
|
||||
Timber.tag(loggerTag.value).w("shareHistoryKeys: no session with this device, probably because there were no one-time keys")
|
||||
return
|
||||
}
|
||||
|
||||
val export = inboundSessionWrapper.mutex.withLock {
|
||||
inboundSessionWrapper.wrapper.exportKeys()
|
||||
} ?: return Unit.also {
|
||||
Timber.tag(loggerTag.value).e("shareHistoryKeys: failed to export group session ${inboundSessionWrapper.wrapper.safeSessionId}")
|
||||
}
|
||||
|
||||
val payloadJson = mapOf(
|
||||
"type" to EventType.FORWARDED_ROOM_KEY,
|
||||
"content" to export
|
||||
)
|
||||
|
||||
val encodedPayload =
|
||||
withContext(coroutineDispatchers.computation) {
|
||||
messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||
}
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||
Timber.tag(loggerTag.value)
|
||||
.d("shareHistoryKeys() : sending session ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
withContext(coroutineDispatchers.io) {
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeviceInRoomInfo(
|
||||
val allowedDevices: MXUsersDevicesMap<CryptoDeviceInfo> = MXUsersDevicesMap(),
|
||||
val withHeldDevices: MXUsersDevicesMap<WithHeldCode> = MXUsersDevicesMap()
|
||||
|
@ -28,6 +28,7 @@ internal class MXOutboundSessionInfo(
|
||||
private val clock: Clock,
|
||||
// When the session was created
|
||||
private val creationTime: Long = clock.epochMillis(),
|
||||
val sharedHistory: Boolean = false
|
||||
) {
|
||||
|
||||
// Number of times this session has been used
|
||||
|
@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
@ -50,6 +51,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.api.util.awaitCallback
|
||||
import org.matrix.android.sdk.api.util.fromBase64
|
||||
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionStore
|
||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.ObjectSigner
|
||||
@ -71,7 +73,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDa
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
@ -118,6 +120,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
||||
// Task executor
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val inboundGroupSessionStore: InboundGroupSessionStore,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
) : KeysBackupService {
|
||||
@ -1316,7 +1319,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
|
||||
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
|
||||
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
|
||||
val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach
|
||||
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
|
||||
|
||||
try {
|
||||
encryptGroupSession(olmInboundGroupSessionWrapper)
|
||||
@ -1405,13 +1408,22 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
|
||||
@VisibleForTesting
|
||||
@WorkerThread
|
||||
fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? {
|
||||
suspend fun encryptGroupSession(olmInboundGroupSessionWrapper: MXInboundMegolmSessionWrapper): KeyBackupData? {
|
||||
olmInboundGroupSessionWrapper.safeSessionId ?: return null
|
||||
olmInboundGroupSessionWrapper.senderKey ?: return null
|
||||
// Gather information for each key
|
||||
val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) }
|
||||
|
||||
// Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at
|
||||
// https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format
|
||||
val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null
|
||||
val sessionData = inboundGroupSessionStore
|
||||
.getInboundGroupSession(olmInboundGroupSessionWrapper.safeSessionId, olmInboundGroupSessionWrapper.senderKey)
|
||||
?.let {
|
||||
withContext(coroutineDispatchers.computation) {
|
||||
it.mutex.withLock { it.wrapper.exportKeys() }
|
||||
}
|
||||
}
|
||||
?: return null
|
||||
val sessionBackupData = mapOf(
|
||||
"algorithm" to sessionData.algorithm,
|
||||
"sender_key" to sessionData.senderKey,
|
||||
@ -1425,7 +1437,9 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
.toJson(sessionBackupData)
|
||||
|
||||
val encryptedSessionBackupData = try {
|
||||
backupOlmPkEncryption?.encrypt(json)
|
||||
withContext(coroutineDispatchers.computation) {
|
||||
backupOlmPkEncryption?.encrypt(json)
|
||||
}
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "OlmException")
|
||||
null
|
||||
@ -1435,12 +1449,12 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
// Build backup data for that key
|
||||
return KeyBackupData(
|
||||
firstMessageIndex = try {
|
||||
olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0
|
||||
olmInboundGroupSessionWrapper.session.firstKnownIndex
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "OlmException")
|
||||
0L
|
||||
},
|
||||
forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size,
|
||||
forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size,
|
||||
isVerified = device?.isVerified == true,
|
||||
|
||||
sessionData = mapOf(
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class InboundGroupSessionData(
|
||||
|
||||
/** The room in which this session is used. */
|
||||
@Json(name = "room_id")
|
||||
var roomId: String? = null,
|
||||
|
||||
/** The base64-encoded curve25519 key of the sender. */
|
||||
@Json(name = "sender_key")
|
||||
var senderKey: String? = null,
|
||||
|
||||
/** Other keys the sender claims. */
|
||||
@Json(name = "keys_claimed")
|
||||
var keysClaimed: Map<String, String>? = null,
|
||||
|
||||
/** Devices which forwarded this session to us (normally emty). */
|
||||
@Json(name = "forwarding_curve25519_key_chain")
|
||||
var forwardingCurve25519KeyChain: List<String>? = emptyList(),
|
||||
|
||||
/** Not yet used, will be in backup v2
|
||||
val untrusted?: Boolean = false */
|
||||
|
||||
/**
|
||||
* Flag that indicates whether or not the current inboundSession will be shared to
|
||||
*invited users to decrypt past messages
|
||||
*/
|
||||
@Json(name = "shared_history")
|
||||
val sharedHistory: Boolean = false,
|
||||
|
||||
)
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.model
|
||||
|
||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import org.matrix.olm.OlmInboundGroupSession
|
||||
import timber.log.Timber
|
||||
|
||||
data class MXInboundMegolmSessionWrapper(
|
||||
// olm object
|
||||
val session: OlmInboundGroupSession,
|
||||
// data about the session
|
||||
val sessionData: InboundGroupSessionData
|
||||
) {
|
||||
// shortcut
|
||||
val roomId = sessionData.roomId
|
||||
val senderKey = sessionData.senderKey
|
||||
val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() }
|
||||
|
||||
/**
|
||||
* Export the inbound group session keys
|
||||
* @param index the index to export. If null, the first known index will be used
|
||||
* @return the inbound group session as MegolmSessionData if the operation succeeds
|
||||
*/
|
||||
internal fun exportKeys(index: Long? = null): MegolmSessionData? {
|
||||
return try {
|
||||
val keysClaimed = sessionData.keysClaimed ?: return null
|
||||
val wantedIndex = index ?: session.firstKnownIndex
|
||||
|
||||
MegolmSessionData(
|
||||
senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"),
|
||||
forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(),
|
||||
senderKey = session.export(index ?: session.firstKnownIndex),
|
||||
senderClaimedKeys = keysClaimed,
|
||||
roomId = sessionData.roomId,
|
||||
sessionId = session.sessionIdentifier(),
|
||||
sessionKey = session.export(wantedIndex),
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
|
||||
sharedHistory = sessionData.sharedHistory
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## Failed to export megolm : sessionID ${tryOrNull { session.sessionIdentifier() }} failed")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* @exportFormat true if the megolm keys are in export format
|
||||
* (ie, they lack an ed25519 signature)
|
||||
*/
|
||||
@Throws
|
||||
internal fun newFromMegolmData(megolmSessionData: MegolmSessionData, exportFormat: Boolean): MXInboundMegolmSessionWrapper {
|
||||
val exportedKey = megolmSessionData.sessionKey ?: throw IllegalArgumentException("key data not found")
|
||||
val inboundSession = if (exportFormat) {
|
||||
OlmInboundGroupSession.importSession(exportedKey)
|
||||
} else {
|
||||
OlmInboundGroupSession(exportedKey)
|
||||
}
|
||||
.also {
|
||||
if (it.sessionIdentifier() != megolmSessionData.sessionId) {
|
||||
it.releaseSession()
|
||||
throw IllegalStateException("Mismatched group session Id")
|
||||
}
|
||||
}
|
||||
val data = InboundGroupSessionData(
|
||||
roomId = megolmSessionData.roomId,
|
||||
senderKey = megolmSessionData.senderKey,
|
||||
keysClaimed = megolmSessionData.senderClaimedKeys,
|
||||
forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain,
|
||||
sharedHistory = megolmSessionData.sharedHistory,
|
||||
)
|
||||
|
||||
return MXInboundMegolmSessionWrapper(
|
||||
inboundSession,
|
||||
data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ import java.io.Serializable
|
||||
* This class adds more context to a OlmInboundGroupSession object.
|
||||
* This allows additional checks. The class implements Serializable so that the context can be stored.
|
||||
*/
|
||||
// Note used anymore, just for database migration
|
||||
@Deprecated("Use MXInboundMegolmSessionWrapper")
|
||||
internal class OlmInboundGroupSessionWrapper2 : Serializable {
|
||||
|
||||
// The associated olm inbound group session.
|
||||
|
@ -20,5 +20,9 @@ import org.matrix.olm.OlmOutboundGroupSession
|
||||
|
||||
internal data class OutboundGroupSessionWrapper(
|
||||
val outboundGroupSession: OlmOutboundGroupSession,
|
||||
val creationTime: Long
|
||||
val creationTime: Long,
|
||||
/**
|
||||
* As per MSC 3061, declares if this key could be shared when inviting a new user to the room
|
||||
*/
|
||||
val sharedHistory: Boolean = false
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||
@ -64,7 +64,7 @@ internal interface IMXCryptoStore {
|
||||
*
|
||||
* @return the list of all known group sessions, to export them.
|
||||
*/
|
||||
fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2>
|
||||
fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper>
|
||||
|
||||
/**
|
||||
* Retrieve the known inbound group sessions for the specified room
|
||||
@ -72,7 +72,7 @@ internal interface IMXCryptoStore {
|
||||
* @param roomId The roomId that the sessions will be returned
|
||||
* @return the list of all known group sessions, for the provided roomId
|
||||
*/
|
||||
fun getInboundGroupSessions(roomId: String): List<OlmInboundGroupSessionWrapper2>
|
||||
fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper>
|
||||
|
||||
/**
|
||||
* @return true to unilaterally blacklist all unverified devices.
|
||||
@ -309,7 +309,7 @@ internal interface IMXCryptoStore {
|
||||
*
|
||||
* @param sessions the inbound group sessions to store.
|
||||
*/
|
||||
fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>)
|
||||
fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>)
|
||||
|
||||
/**
|
||||
* Retrieve an inbound group session.
|
||||
@ -318,7 +318,7 @@ internal interface IMXCryptoStore {
|
||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||
* @return an inbound group session.
|
||||
*/
|
||||
fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2?
|
||||
fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?
|
||||
|
||||
/**
|
||||
* Retrieve an inbound group session, filtering shared history.
|
||||
@ -328,7 +328,7 @@ internal interface IMXCryptoStore {
|
||||
* @param sharedHistory filter inbound session with respect to shared history field
|
||||
* @return an inbound group session.
|
||||
*/
|
||||
fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): OlmInboundGroupSessionWrapper2?
|
||||
fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper?
|
||||
|
||||
/**
|
||||
* Get the current outbound group session for this encrypted room
|
||||
@ -340,13 +340,6 @@ internal interface IMXCryptoStore {
|
||||
*/
|
||||
fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?)
|
||||
|
||||
/**
|
||||
* Returns true if there is a room history visibility change since the latest outbound
|
||||
* session. Specifically when the room's history visibility setting changes to
|
||||
* world_readable or shared from invited or joined, or changes to invited or joined from world_readable or shared
|
||||
*/
|
||||
fun needsRotationDueToVisibilityChange(roomId: String): Boolean
|
||||
|
||||
/**
|
||||
* Remove an inbound group session
|
||||
*
|
||||
@ -369,7 +362,7 @@ internal interface IMXCryptoStore {
|
||||
*
|
||||
* @param olmInboundGroupSessionWrappers the sessions
|
||||
*/
|
||||
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>)
|
||||
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<MXInboundMegolmSessionWrapper>)
|
||||
|
||||
/**
|
||||
* Retrieve inbound group sessions that are not yet backed up.
|
||||
@ -377,7 +370,7 @@ internal interface IMXCryptoStore {
|
||||
* @param limit the maximum number of sessions to return.
|
||||
* @return an array of non backed up inbound group sessions.
|
||||
*/
|
||||
fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2>
|
||||
fun inboundGroupSessionsToBackup(limit: Int): List<MXInboundMegolmSessionWrapper>
|
||||
|
||||
/**
|
||||
* Number of stored inbound group sessions.
|
||||
|
@ -50,7 +50,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldCo
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
@ -671,6 +671,8 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.v("setShouldShareHistory for room $roomId is $shouldShareHistory")
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory
|
||||
}
|
||||
@ -740,61 +742,55 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) {
|
||||
override fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>) {
|
||||
if (sessions.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
sessions.forEach { session ->
|
||||
var sessionIdentifier: String? = null
|
||||
sessions.forEach { wrapper ->
|
||||
|
||||
try {
|
||||
sessionIdentifier = session.olmInboundGroupSession?.sessionIdentifier()
|
||||
val sessionIdentifier = try {
|
||||
wrapper.session.sessionIdentifier()
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "## storeInboundGroupSession() : sessionIdentifier failed")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if (sessionIdentifier != null) {
|
||||
val shouldShareHistory = session.roomId?.let { roomId ->
|
||||
CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory
|
||||
} ?: false
|
||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey)
|
||||
// val shouldShareHistory = session.roomId?.let { roomId ->
|
||||
// CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory
|
||||
// } ?: false
|
||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, wrapper.sessionData.senderKey)
|
||||
|
||||
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
|
||||
primaryKey = key
|
||||
sessionId = sessionIdentifier
|
||||
senderKey = session.senderKey
|
||||
roomId = session.roomId
|
||||
sharedHistory = shouldShareHistory
|
||||
putInboundGroupSession(session)
|
||||
}
|
||||
Timber.i("## CRYPTO | shouldShareHistory: $shouldShareHistory for $key")
|
||||
realm.insertOrUpdate(realmOlmInboundGroupSession)
|
||||
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
|
||||
primaryKey = key
|
||||
store(wrapper)
|
||||
}
|
||||
Timber.i("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key")
|
||||
realm.insertOrUpdate(realmOlmInboundGroupSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? {
|
||||
override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? {
|
||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
|
||||
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
realm.where<OlmInboundGroupSessionEntity>()
|
||||
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
|
||||
.findFirst()
|
||||
?.getInboundGroupSession()
|
||||
?.toModel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): OlmInboundGroupSessionWrapper2? {
|
||||
override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? {
|
||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
.equalTo(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, sharedHistory)
|
||||
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
|
||||
.findFirst()
|
||||
?.getInboundGroupSession()
|
||||
?.toModel()
|
||||
}
|
||||
}
|
||||
|
||||
@ -806,7 +802,8 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
entity.getOutboundGroupSession()?.let {
|
||||
OutboundGroupSessionWrapper(
|
||||
it,
|
||||
entity.creationTime ?: 0
|
||||
entity.creationTime ?: 0,
|
||||
entity.shouldShareHistory
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -836,36 +833,32 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun needsRotationDueToVisibilityChange(roomId: String): Boolean {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
CryptoRoomEntity.getById(realm, roomId)?.let { entity ->
|
||||
entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
// override fun needsRotationDueToVisibilityChange(roomId: String): Boolean {
|
||||
// return doWithRealm(realmConfiguration) { realm ->
|
||||
// CryptoRoomEntity.getById(realm, roomId)?.let { entity ->
|
||||
// entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory
|
||||
// }
|
||||
// } ?: false
|
||||
// }
|
||||
|
||||
/**
|
||||
* Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
|
||||
* so there is no need to use or update `inboundGroupSessionToRelease` for native memory management.
|
||||
*/
|
||||
override fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> {
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
override fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
realm.where<OlmInboundGroupSessionEntity>()
|
||||
.findAll()
|
||||
.mapNotNull { inboundGroupSessionEntity ->
|
||||
inboundGroupSessionEntity.getInboundGroupSession()
|
||||
}
|
||||
.mapNotNull { it.toModel() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInboundGroupSessions(roomId: String): List<OlmInboundGroupSessionWrapper2> {
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
override fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper> {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
realm.where<OlmInboundGroupSessionEntity>()
|
||||
.equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId)
|
||||
.findAll()
|
||||
.mapNotNull { inboundGroupSessionEntity ->
|
||||
inboundGroupSessionEntity.getInboundGroupSession()
|
||||
}
|
||||
.mapNotNull { it.toModel() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -926,7 +919,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>) {
|
||||
override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<MXInboundMegolmSessionWrapper>) {
|
||||
if (olmInboundGroupSessionWrappers.isEmpty()) {
|
||||
return
|
||||
}
|
||||
@ -934,10 +927,13 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
|
||||
try {
|
||||
val sessionIdentifier = olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier()
|
||||
val sessionIdentifier =
|
||||
tryOrNull("Failed to get session identifier") {
|
||||
olmInboundGroupSessionWrapper.session.sessionIdentifier()
|
||||
} ?: return@forEach
|
||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(
|
||||
sessionIdentifier,
|
||||
olmInboundGroupSessionWrapper.senderKey
|
||||
olmInboundGroupSessionWrapper.sessionData.senderKey
|
||||
)
|
||||
|
||||
val existing = realm.where<OlmInboundGroupSessionEntity>()
|
||||
@ -950,9 +946,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
// ... might be in cache but not yet persisted, create a record to persist backedup state
|
||||
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
|
||||
primaryKey = key
|
||||
sessionId = sessionIdentifier
|
||||
senderKey = olmInboundGroupSessionWrapper.senderKey
|
||||
putInboundGroupSession(olmInboundGroupSessionWrapper)
|
||||
store(olmInboundGroupSessionWrapper)
|
||||
backedUp = true
|
||||
}
|
||||
|
||||
@ -965,15 +959,13 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2> {
|
||||
override fun inboundGroupSessionsToBackup(limit: Int): List<MXInboundMegolmSessionWrapper> {
|
||||
return doWithRealm(realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
.equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false)
|
||||
.limit(limit.toLong())
|
||||
.findAll()
|
||||
.mapNotNull { inboundGroupSession ->
|
||||
inboundGroupSession.getInboundGroupSession()
|
||||
}
|
||||
.mapNotNull { it.toModel() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,20 +17,66 @@
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
// Version 16L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061
|
||||
internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 16) {
|
||||
/**
|
||||
* Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061
|
||||
* Also migrates how megolm session are stored to avoid additional serialized frozen class
|
||||
*/
|
||||
internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)
|
||||
|
||||
val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
|
||||
|
||||
realm.schema.get("OlmInboundGroupSessionEntity")
|
||||
?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java)
|
||||
?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java)
|
||||
?.addField(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java)
|
||||
?.addField(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java)
|
||||
?.transform { dynamicObject ->
|
||||
try {
|
||||
// we want to convert the old wrapper frozen class into a
|
||||
// map of sessionData & the pickled session herself
|
||||
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData ->
|
||||
val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") {
|
||||
@Suppress("DEPRECATION")
|
||||
deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(oldData)
|
||||
}
|
||||
val groupSession = oldWrapper?.olmInboundGroupSession
|
||||
?: return@transform Unit.also {
|
||||
Timber.w("Failed to migrate megolm session, no olmInboundGroupSession")
|
||||
}
|
||||
// now convert to new data
|
||||
val data = InboundGroupSessionData(
|
||||
senderKey = oldWrapper.senderKey,
|
||||
roomId = oldWrapper.roomId,
|
||||
keysClaimed = oldWrapper.keysClaimed,
|
||||
forwardingCurve25519KeyChain = oldWrapper.forwardingCurve25519KeyChain,
|
||||
sharedHistory = false,
|
||||
)
|
||||
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)
|
||||
dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(data))
|
||||
dynamicObject.setString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, serializeForRealm(groupSession))
|
||||
|
||||
// denormalized fields
|
||||
dynamicObject.setString(OlmInboundGroupSessionEntityFields.ROOM_ID, oldWrapper.roomId)
|
||||
dynamicObject.setBoolean(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, false)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Failed to migrate megolm session")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,12 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.olm.OlmInboundGroupSession
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"
|
||||
@ -28,11 +31,23 @@ internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId:
|
||||
internal open class OlmInboundGroupSessionEntity(
|
||||
// Combined value to build a primary key
|
||||
@PrimaryKey var primaryKey: String? = null,
|
||||
|
||||
// denormalization for faster querying (these fields are in the inboundGroupSessionDataJson)
|
||||
var sessionId: String? = null,
|
||||
var senderKey: String? = null,
|
||||
var roomId: String? = null,
|
||||
// olmInboundGroupSessionData contains Json
|
||||
|
||||
// Deprecated, used for migration / olmInboundGroupSessionData contains Json
|
||||
// keep it in case of problem to have a chance to recover
|
||||
var olmInboundGroupSessionData: String? = null,
|
||||
|
||||
// Stores the session data in an extensible format
|
||||
// to allow to store data not yet supported for later use
|
||||
var inboundGroupSessionDataJson: String? = null,
|
||||
|
||||
// The pickled session
|
||||
var serializedOlmInboundGroupSession: String? = null,
|
||||
|
||||
// Flag that indicates whether or not the current inboundSession will be shared to
|
||||
// invited users to decrypt past messages
|
||||
var sharedHistory: Boolean = false,
|
||||
@ -41,18 +56,58 @@ internal open class OlmInboundGroupSessionEntity(
|
||||
) :
|
||||
RealmObject() {
|
||||
|
||||
fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
|
||||
fun store(wrapper: MXInboundMegolmSessionWrapper) {
|
||||
this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session)
|
||||
this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData)
|
||||
this.roomId = wrapper.sessionData.roomId
|
||||
this.senderKey = wrapper.sessionData.senderKey
|
||||
this.sessionId = wrapper.session.sessionIdentifier()
|
||||
this.sharedHistory = wrapper.sessionData.sharedHistory
|
||||
}
|
||||
// fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
|
||||
// return try {
|
||||
// deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
|
||||
// } catch (failure: Throwable) {
|
||||
// Timber.e(failure, "## Deserialization failure")
|
||||
// return null
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
|
||||
// olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
|
||||
// }
|
||||
|
||||
fun getOlmGroupSession(): OlmInboundGroupSession? {
|
||||
return try {
|
||||
deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
|
||||
deserializeFromRealm(serializedOlmInboundGroupSession)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## Deserialization failure")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
|
||||
olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
|
||||
fun getData(): InboundGroupSessionData? {
|
||||
return try {
|
||||
inboundGroupSessionDataJson?.let {
|
||||
adapter.fromJson(it)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## Deserialization failure")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
fun toModel(): MXInboundMegolmSessionWrapper? {
|
||||
val data = getData() ?: return null
|
||||
val session = getOlmGroupSession() ?: return null
|
||||
return MXInboundMegolmSessionWrapper(
|
||||
session = session,
|
||||
sessionData = data
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val adapter = MoshiProvider.providesMoshi()
|
||||
.adapter(InboundGroupSessionData::class.java)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.crypto.tasks
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
@ -48,8 +47,12 @@ internal class DefaultSendEventTask @Inject constructor(
|
||||
params.event.roomId
|
||||
?.takeIf { params.encrypt }
|
||||
?.let { roomId ->
|
||||
tryOrNull {
|
||||
try {
|
||||
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
|
||||
} catch (failure: Throwable) {
|
||||
// send any way?
|
||||
// the result is that some users won't probably be able to decrypt :/
|
||||
Timber.w(failure, "SendEvent: failed to load members in room ${params.event.roomId}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +144,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override suspend fun invite(userId: String, reason: String?) {
|
||||
// TODO not sure it's the right way to get the latest messages in a room
|
||||
val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
ChunkEntity.findLatestSessionInfo(it, roomId)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user