Skip to content

Commit d9f64ad

Browse files
committed
share keys for history take2
1 parent ffc7074 commit d9f64ad

31 files changed

+647
-309
lines changed

Diff for: matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
8181
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
8282

8383
val roomId = testHelper.runBlockingTest {
84-
aliceSession.createRoom(CreateRoomParams().apply {
84+
aliceSession.roomService().createRoom(CreateRoomParams().apply {
8585
historyVisibility = roomHistoryVisibility
8686
name = "MyRoom"
8787
})

Diff for: matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt

+65-17
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
1919
import android.util.Log
2020
import androidx.test.filters.LargeTest
2121
import org.amshove.kluent.internal.assertEquals
22+
import org.amshove.kluent.internal.assertNotEquals
2223
import org.junit.Assert
2324
import org.junit.FixMethodOrder
2425
import org.junit.Test
@@ -101,7 +102,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
101102
// Bob should be able to decrypt the message
102103
testHelper.waitWithLatch { latch ->
103104
testHelper.retryPeriodicallyWithLatch(latch) {
104-
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
105+
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
105106
(timelineEvent != null &&
106107
timelineEvent.isEncrypted() &&
107108
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@@ -119,7 +120,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
119120
// Alice invites new user to the room
120121
testHelper.runBlockingTest {
121122
Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}")
122-
aliceRoomPOV.invite(arisSession.myUserId)
123+
aliceRoomPOV.membershipService().invite(arisSession.myUserId)
123124
}
124125

125126
waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper)
@@ -135,7 +136,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
135136
// Aris should be able to decrypt the message
136137
testHelper.waitWithLatch { latch ->
137138
testHelper.retryPeriodicallyWithLatch(latch) {
138-
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
139+
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
139140
(timelineEvent != null &&
140141
timelineEvent.isEncrypted() &&
141142
timelineEvent.root.getClearType() == EventType.MESSAGE
@@ -152,7 +153,9 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
152153
// Aris should not even be able to get the message
153154
testHelper.waitWithLatch { latch ->
154155
testHelper.retryPeriodicallyWithLatch(latch) {
155-
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
156+
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
157+
?.timelineService()
158+
?.getTimelineEvent(aliceMessageId!!)
156159
timelineEvent == null
157160
}
158161
}
@@ -242,7 +245,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
242245
// Alice
243246
val aliceSession = cryptoTestData.firstSession
244247
val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
245-
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
248+
// val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
246249

247250
// Bob
248251
val bobSession = cryptoTestData.secondSession
@@ -256,35 +259,62 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
256259
Log.v("#E2E TEST ROTATION", "Alice sent message to roomId: $e2eRoomID")
257260

258261
// Bob should be able to decrypt the message
262+
var firstAliceMessageMegolmSessionId: String? = null
259263
testHelper.waitWithLatch { latch ->
260264
testHelper.retryPeriodicallyWithLatch(latch) {
261-
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
265+
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)
266+
?.timelineService()
267+
?.getTimelineEvent(aliceMessageId!!)
262268
(timelineEvent != null &&
263269
timelineEvent.isEncrypted() &&
264270
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
265271
if (it) {
272+
firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String
266273
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
267274
}
268275
}
269276
}
270277
}
271278

272-
// Rotation has already been done so we do not need to rotate again
273-
assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false)
279+
Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
280+
281+
var secondAliceMessageSessionId: String? = null
282+
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage ->
283+
testHelper.waitWithLatch { latch ->
284+
testHelper.retryPeriodicallyWithLatch(latch) {
285+
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)
286+
?.timelineService()
287+
?.getTimelineEvent(secondMessage)
288+
(timelineEvent != null &&
289+
timelineEvent.isEncrypted() &&
290+
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
291+
if (it) {
292+
secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
293+
}
294+
}
295+
}
296+
}
297+
}
298+
assertEquals("No rotation needed session should be the same", firstAliceMessageMegolmSessionId, secondAliceMessageSessionId)
274299
Log.v("#E2E TEST ROTATION", "No rotation needed yet")
275300

276301
// Let's change the room history visibility
277302
testHelper.waitWithLatch {
278-
aliceRoomPOV.sendStateEvent(
279-
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
280-
stateKey = "",
281-
body = RoomHistoryVisibilityContent(_historyVisibility = nextRoomHistoryVisibility._historyVisibility).toContent()
282-
)
303+
aliceRoomPOV.stateService()
304+
.sendStateEvent(
305+
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
306+
stateKey = "",
307+
body = RoomHistoryVisibilityContent(
308+
_historyVisibility = nextRoomHistoryVisibility._historyVisibility
309+
).toContent()
310+
)
283311
it.countDown()
284312
}
313+
285314
testHelper.waitWithLatch { latch ->
286315
testHelper.retryPeriodicallyWithLatch(latch) {
287316
val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
317+
.stateService()
288318
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)
289319
?.content
290320
?.toModel<RoomHistoryVisibilityContent>()
@@ -293,13 +323,31 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
293323
}
294324
}
295325

326+
var aliceThirdMessageSessionId: String? = null
327+
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage ->
328+
testHelper.waitWithLatch { latch ->
329+
testHelper.retryPeriodicallyWithLatch(latch) {
330+
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)
331+
?.timelineService()
332+
?.getTimelineEvent(thirdMessage)
333+
(timelineEvent != null &&
334+
timelineEvent.isEncrypted() &&
335+
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
336+
if (it) {
337+
aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
338+
}
339+
}
340+
}
341+
}
342+
}
343+
296344
when {
297345
initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> {
298-
assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false)
346+
assertEquals("Session shouldn't have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId)
299347
Log.v("#E2E TEST ROTATION", "Rotation is not needed")
300348
}
301349
initRoomHistoryVisibility.shouldShareHistory() != nextRoomHistoryVisibility.historyVisibility!!.shouldShareHistory() -> {
302-
assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), true)
350+
assertNotEquals("Session should have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId)
303351
Log.v("#E2E TEST ROTATION", "Rotation is needed!")
304352
}
305353
}
@@ -308,10 +356,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
308356
}
309357

310358
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? {
311-
aliceRoomPOV.sendTextMessage(text)
359+
aliceRoomPOV.sendService().sendTextMessage(text)
312360
var sentEventId: String? = null
313361
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
314-
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
362+
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
315363
timeline.start()
316364
testHelper.retryPeriodicallyWithLatch(latch) {
317365
val decryptedMsg = timeline.getSnapshot()

Diff for: matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class PreShareKeysTest : InstrumentedTest {
7676
assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
7777
assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)
7878

79-
val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier()
79+
val megolmSessionId = bobInboundForAlice.session.sessionIdentifier()
8080

8181
assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId)
8282

Diff for: matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
1919
import org.matrix.android.sdk.api.session.Session
2020
import org.matrix.android.sdk.common.CommonTestHelper
2121
import org.matrix.android.sdk.common.CryptoTestData
22-
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
22+
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
2323

2424
/**
2525
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
2626
*/
2727
internal data class KeysBackupScenarioData(
2828
val cryptoTestData: CryptoTestData,
29-
val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
29+
val aliceKeys: List<MXInboundMegolmSessionWrapper>,
3030
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
3131
val aliceSession2: Session
3232
) {

Diff for: matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ class KeysBackupTest : InstrumentedTest {
263263
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
264264

265265
// - Check encryptGroupSession() returns stg
266-
val keyBackupData = keysBackup.encryptGroupSession(session)
266+
val keyBackupData = testHelper.runBlockingTest { keysBackup.encryptGroupSession(session) }
267267
assertNotNull(keyBackupData)
268268
assertNotNull(keyBackupData!!.sessionData)
269269

@@ -274,7 +274,7 @@ class KeysBackupTest : InstrumentedTest {
274274
val sessionData = keysBackup
275275
.decryptKeyBackupData(
276276
keyBackupData,
277-
session.olmInboundGroupSession!!.sessionIdentifier(),
277+
session.safeSessionId!!,
278278
cryptoTestData.roomId,
279279
decryption!!
280280
)

Diff for: matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ internal class KeysBackupTestHelper(
182182
// - Alice must have the same keys on both devices
183183
for (aliceKey1 in testData.aliceKeys) {
184184
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
185-
.getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
185+
.getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
186186
Assert.assertNotNull(aliceKey2)
187187
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
188188
}

Diff for: matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,5 @@ interface CryptoService {
179179
/**
180180
* Share all inbound sessions of the last chunk messages to the provided userId devices
181181
*/
182-
fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?)
182+
suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?)
183183
}

Diff for: matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,12 @@ data class ForwardedRoomKeyContent(
6969
* private part of this key unless they have done device verification.
7070
*/
7171
@Json(name = "sender_claimed_ed25519_key")
72-
val senderClaimedEd25519Key: String? = null
72+
val senderClaimedEd25519Key: String? = null,
73+
74+
/**
75+
* MSC3061
76+
* Identifies keys that were sent when the room's visibility setting was set to world_readable or shared
77+
*/
78+
@Json(name = "org.matrix.msc3061.shared_history")
79+
val sharedHistory: Boolean? = false,
7380
)

Diff for: matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt

+9-1
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,13 @@ data class RoomKeyContent(
3838

3939
// should be a Long but it is sometimes a double
4040
@Json(name = "chain_index")
41-
val chainIndex: Any? = null
41+
val chainIndex: Any? = null,
42+
43+
/**
44+
* MSC3061
45+
* Identifies keys that were sent when the room's visibility setting was set to world_readable or shared
46+
*/
47+
@Json(name = "org.matrix.msc3061.shared_history")
48+
val sharedHistory: Boolean? = false
49+
4250
)

Diff for: matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt

+28-34
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import org.matrix.olm.OlmManager
110110
import timber.log.Timber
111111
import java.util.concurrent.atomic.AtomicBoolean
112112
import javax.inject.Inject
113+
import kotlin.coroutines.coroutineContext
113114
import kotlin.math.max
114115

115116
/**
@@ -957,10 +958,13 @@ internal class DefaultCryptoService @Inject constructor(
957958
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
958959
if (!event.isStateEvent()) return
959960
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
960-
eventContent?.historyVisibility?.let {
961-
cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED)
962-
cryptoStore.setShouldShareHistory(roomId, it.shouldShareHistory())
963-
} ?: cryptoStore.setShouldShareHistory(roomId, false)
961+
val historyVisibility = eventContent?.historyVisibility
962+
if (historyVisibility == null) {
963+
cryptoStore.setShouldShareHistory(roomId, false)
964+
} else {
965+
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
966+
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
967+
}
964968
}
965969

966970
/**
@@ -1332,36 +1336,26 @@ internal class DefaultCryptoService @Inject constructor(
13321336
}
13331337
}
13341338

1335-
override fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) {
1336-
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
1337-
runCatching {
1338-
deviceListManager.downloadKeys(listOf(userId), false)
1339-
}.mapCatching {
1340-
val userDevices = cryptoStore.getUserDevices(userId)
1341-
userDevices?.forEach {
1342-
// Lets share the provided inbound sessions for every user device
1343-
val deviceId = it.key
1344-
sessionInfoSet?.mapNotNull { sessionInfo ->
1345-
// Get inbound session from sessionId and sessionKey
1346-
cryptoStore.getInboundGroupSession(
1347-
sessionId = sessionInfo.sessionId,
1348-
senderKey = sessionInfo.senderKey,
1349-
sharedHistory = true
1350-
)
1351-
}?.filter { inboundGroupSession ->
1352-
// Prevent injecting a forged encrypted message and using session_id/sender_key of another room.
1353-
(inboundGroupSession.roomId == roomId).also {
1354-
Timber.tag(loggerTag.value).d("Forged encrypted message detected for roomId:$roomId")
1355-
}
1356-
}?.forEach { inboundGroupSession ->
1357-
// Share the sharable session to userId with deviceId
1358-
val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true)
1359-
val algorithm = exportedKeys?.algorithm
1360-
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm)
1361-
decryptor?.shareForwardKeysWithDevice(exportedKeys, deviceId, userId)
1362-
Timber.i("## CRYPTO | Sharing inbound session")
1363-
}
1364-
}
1339+
override suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) {
1340+
deviceListManager.downloadKeys(listOf(userId), false)
1341+
val userDevices = cryptoStore.getUserDeviceList(userId)
1342+
val sessionToShare = sessionInfoSet.orEmpty().mapNotNull { sessionInfo ->
1343+
// Get inbound session from sessionId and sessionKey
1344+
withContext(coroutineDispatchers.crypto) {
1345+
olmDevice.getInboundGroupSession(
1346+
sessionId = sessionInfo.sessionId,
1347+
senderKey = sessionInfo.senderKey,
1348+
roomId = roomId
1349+
).takeIf { it.wrapper.sessionData.sharedHistory }
1350+
}
1351+
}
1352+
1353+
userDevices?.forEach { deviceInfo ->
1354+
// Lets share the provided inbound sessions for every user device
1355+
sessionToShare.forEach { inboundGroupSession ->
1356+
val encryptor = roomEncryptorsStore.get(roomId)
1357+
encryptor?.shareHistoryKeysWithDevice(inboundGroupSession, deviceInfo)
1358+
Timber.i("## CRYPTO | Sharing inbound session")
13651359
}
13661360
}
13671361
}

0 commit comments

Comments
 (0)