Skip to content

Commit

Permalink
share keys for history take2
Browse files Browse the repository at this point in the history
  • Loading branch information
BillCarsonFr committed May 12, 2022
1 parent ffc7074 commit d9f64ad
Show file tree
Hide file tree
Showing 31 changed files with 647 additions and 309 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class CryptoTestHelper(private 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"
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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>()
Expand All @@ -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!")
}
}
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,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)

Expand All @@ -274,7 +274,7 @@ class KeysBackupTest : InstrumentedTest {
val sessionData = keysBackup
.decryptKeyBackupData(
keyBackupData,
session.olmInboundGroupSession!!.sessionIdentifier(),
session.safeSessionId!!,
cryptoTestData.roomId,
decryption!!
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,5 +179,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>?)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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

)
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -957,10 +958,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())
}
}

/**
Expand Down Expand Up @@ -1332,36 +1336,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")
}
}
}
Expand Down
Loading

0 comments on commit d9f64ad

Please sign in to comment.