Merge branch 'develop' into feature/fullscreen_avatar

This commit is contained in:
Onuray Sahin 2020-02-26 16:23:02 +03:00 committed by GitHub
commit d2efe0e10c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 2410 additions and 1380 deletions

View File

@ -12,6 +12,7 @@ Improvements 🙌:
- Migrate to binary QR code verification (#994) - Migrate to binary QR code verification (#994)
- Share action is added to room profile and room member profile (#858) - Share action is added to room profile and room member profile (#858)
- Display avatar in fullscreen (#861) - Display avatar in fullscreen (#861)
- Fix some performance issues with crypto
Bugfix 🐛: Bugfix 🐛:
- Account creation: wrongly hints that an email can be used to create an account (#941) - Account creation: wrongly hints that an email can be used to create an account (#941)
@ -22,6 +23,7 @@ Bugfix 🐛:
- Leaving a room creates a stuck "leaving room" loading screen. (#1041) - Leaving a room creates a stuck "leaving room" loading screen. (#1041)
- Fix some invitation handling issues (#1013) - Fix some invitation handling issues (#1013)
- New direct chat: selecting a participant sometimes results in two breadcrumbs (#1022) - New direct chat: selecting a participant sometimes results in two breadcrumbs (#1022)
- New direct chat: selecting several participants was not adding the room to the direct chats list
Translations 🗣: Translations 🗣:
- -
@ -29,6 +31,7 @@ Translations 🗣:
SDK API changes ⚠️: SDK API changes ⚠️:
- Get crypto methods through Session.cryptoService() - Get crypto methods through Session.cryptoService()
- ProgressListener.onProgress() function will be invoked on the background thread instead of UI thread - ProgressListener.onProgress() function will be invoked on the background thread instead of UI thread
- Improve CreateRoomParams API (#1070)
Build 🧱: Build 🧱:
- -

View File

@ -38,9 +38,7 @@ class AccountCreationTest : InstrumentedTest {
fun createAccountTest() { fun createAccountTest() {
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
commonTestHelper.signout(session) commonTestHelper.signOutAndClose(session)
session.close()
} }
@Test @Test
@ -50,14 +48,14 @@ class AccountCreationTest : InstrumentedTest {
// Log again to the same account // Log again to the same account
val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
session.close() commonTestHelper.signOutAndClose(session)
session2.close() commonTestHelper.signOutAndClose(session2)
} }
@Test @Test
fun simpleE2eTest() { fun simpleE2eTest() {
val res = cryptoTestHelper.doE2ETestWithAliceInARoom() val res = cryptoTestHelper.doE2ETestWithAliceInARoom()
res.close() res.cleanUp(commonTestHelper)
} }
} }

View File

@ -28,7 +28,10 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationResult
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
@ -113,7 +116,7 @@ class CommonTestHelper(context: Context) {
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> { fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages) val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
val latch = CountDownLatch(nbOfMessages) val latch = CountDownLatch(nbOfMessages)
val onEventSentListener = object : Timeline.Listener { val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) { override fun onTimelineFailure(throwable: Throwable) {
} }
@ -122,20 +125,26 @@ class CommonTestHelper(context: Context) {
} }
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
// TODO Count only new messages? val newMessages = snapshot
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) { .filter { LocalEcho.isLocalEchoId(it.eventId).not() }
sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE }) .filter { it.root.getClearType() == EventType.MESSAGE }
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
if (newMessages.size == nbOfMessages) {
sentEvents.addAll(newMessages)
latch.countDown() latch.countDown()
} }
} }
} }
val timeline = room.createTimeline(null, TimelineSettings(10)) val timeline = room.createTimeline(null, TimelineSettings(10))
timeline.addListener(onEventSentListener) timeline.start()
timeline.addListener(timelineListener)
for (i in 0 until nbOfMessages) { for (i in 0 until nbOfMessages) {
room.sendTextMessage(message + " #" + (i + 1)) room.sendTextMessage(message + " #" + (i + 1))
} }
await(latch) await(latch)
timeline.removeListener(onEventSentListener) timeline.removeListener(timelineListener)
timeline.dispose()
// Check that all events has been created // Check that all events has been created
assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong()) assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong())
@ -283,11 +292,10 @@ class CommonTestHelper(context: Context) {
/** /**
* Clear all provided sessions * Clear all provided sessions
*/ */
fun Iterable<Session>.close() = forEach { it.close() } fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
fun signout(session: Session) { fun signOutAndClose(session: Session) {
val lock = CountDownLatch(1) doSync<Unit> { session.signOut(true, it) }
session.signOut(true, TestMatrixCallback(lock)) session.close()
await(lock)
} }
} }

View File

@ -23,9 +23,9 @@ data class CryptoTestData(val firstSession: Session,
val secondSession: Session? = null, val secondSession: Session? = null,
val thirdSession: Session? = null) { val thirdSession: Session? = null) {
fun close() { fun cleanUp(testHelper: CommonTestHelper) {
firstSession.close() testHelper.signOutAndClose(firstSession)
secondSession?.close() secondSession?.let { testHelper.signOutAndClose(it) }
secondSession?.close() thirdSession?.let { testHelper.signOutAndClose(it) }
} }
} }

View File

@ -41,16 +41,15 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import java.util.Arrays
import java.util.HashMap import java.util.HashMap
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
class CryptoTestHelper(val mTestHelper: CommonTestHelper) { class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val messagesFromAlice: List<String> = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!") private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
val messagesFromBob: List<String> = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
val defaultSessionParams = SessionTestParams(true) private val defaultSessionParams = SessionTestParams(true)
/** /**
* @return alice session * @return alice session
@ -58,34 +57,23 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
fun doE2ETestWithAliceInARoom(): CryptoTestData { fun doE2ETestWithAliceInARoom(): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
var roomId: String? = null val roomId = mTestHelper.doSync<String> {
val lock1 = CountDownLatch(1) aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
}
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) { val room = aliceSession.getRoom(roomId)!!
override fun onSuccess(data: String) {
roomId = data
super.onSuccess(data)
}
})
mTestHelper.await(lock1) mTestHelper.doSync<Unit> {
assertNotNull(roomId) room.enableEncryption(callback = it)
}
val room = aliceSession.getRoom(roomId!!)!! return CryptoTestData(aliceSession, roomId)
val lock2 = CountDownLatch(1)
room.enableEncryption(callback = TestMatrixCallback(lock2))
mTestHelper.await(lock2)
return CryptoTestData(aliceSession, roomId!!)
} }
/** /**
* @return alice and bob sessions * @return alice and bob sessions
*/ */
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData { fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
val statuses = HashMap<String, String>()
val cryptoTestData = doE2ETestWithAliceInARoom() val cryptoTestData = doE2ETestWithAliceInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId val aliceRoomId = cryptoTestData.roomId
@ -94,7 +82,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
val lock1 = CountDownLatch(2) val lock1 = CountDownLatch(1)
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) { val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
@ -103,7 +91,6 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val newRoomObserver = object : Observer<List<RoomSummary>> { val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) { override fun onChanged(t: List<RoomSummary>?) {
if (t?.isNotEmpty() == true) { if (t?.isNotEmpty() == true) {
statuses["onNewRoom"] = "onNewRoom"
lock1.countDown() lock1.countDown()
bobRoomSummariesLive.removeObserver(this) bobRoomSummariesLive.removeObserver(this)
} }
@ -114,26 +101,20 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(newRoomObserver) bobRoomSummariesLive.observeForever(newRoomObserver)
} }
aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) { mTestHelper.doSync<Unit> {
override fun onSuccess(data: Unit) { aliceRoom.invite(bobSession.myUserId, callback = it)
statuses["invite"] = "invite" }
super.onSuccess(data)
}
})
mTestHelper.await(lock1) mTestHelper.await(lock1)
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) val lock = CountDownLatch(1)
val lock2 = CountDownLatch(2)
val roomJoinedObserver = object : Observer<List<RoomSummary>> { val roomJoinedObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) { override fun onChanged(t: List<RoomSummary>?) {
if (bobSession.getRoom(aliceRoomId) if (bobSession.getRoom(aliceRoomId)
?.getRoomMember(aliceSession.myUserId) ?.getRoomMember(aliceSession.myUserId)
?.membership == Membership.JOIN) { ?.membership == Membership.JOIN) {
statuses["AliceJoin"] = "AliceJoin" lock.countDown()
lock2.countDown()
bobRoomSummariesLive.removeObserver(this) bobRoomSummariesLive.removeObserver(this)
} }
} }
@ -143,19 +124,15 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(roomJoinedObserver) bobRoomSummariesLive.observeForever(roomJoinedObserver)
} }
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2)) mTestHelper.doSync<Unit> { bobSession.joinRoom(aliceRoomId, callback = it) }
mTestHelper.await(lock2) mTestHelper.await(lock)
// Ensure bob can send messages to the room // Ensure bob can send messages to the room
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! // val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
// assertNotNull(roomFromBobPOV.powerLevels) // assertNotNull(roomFromBobPOV.powerLevels)
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) // assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin"))
// bobSession.dataHandler.removeListener(bobEventListener)
return CryptoTestData(aliceSession, aliceRoomId, bobSession) return CryptoTestData(aliceSession, aliceRoomId, bobSession)
} }
@ -237,7 +214,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
var lock = CountDownLatch(1) val lock = CountDownLatch(1)
val bobEventsListener = object : Timeline.Listener { val bobEventsListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) { override fun onTimelineFailure(throwable: Throwable) {
@ -249,63 +226,35 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
} }
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE } val messages = snapshot.filter { it.root.getClearType() == EventType.MESSAGE }
.size .groupBy { it.root.senderId!! }
if (size == 3) { // Alice has sent 2 messages and Bob has sent 3 messages
if (messages[aliceSession.myUserId]?.size == 2 && messages[bobSession.myUserId]?.size == 3) {
lock.countDown() lock.countDown()
} }
} }
} }
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10)) val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
bobTimeline.start()
bobTimeline.addListener(bobEventsListener) bobTimeline.addListener(bobEventsListener)
val results = HashMap<String, Any>()
// bobSession.dataHandler.addListener(object : MXEventListener() {
// override fun onToDeviceEvent(event: Event) {
// results["onToDeviceEvent"] = event
// lock.countDown()
// }
// })
// Alice sends a message // Alice sends a message
roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
assertTrue(results.containsKey("onToDeviceEvent"))
// assertEquals(1, messagesReceivedByBobCount)
// Bob send a message // Bob send 3 messages
lock = CountDownLatch(1)
roomFromBobPOV.sendTextMessage(messagesFromBob[0]) roomFromBobPOV.sendTextMessage(messagesFromBob[0])
// android does not echo the messages sent from itself
// messagesReceivedByBobCount++
mTestHelper.await(lock)
// assertEquals(2, messagesReceivedByBobCount)
// Bob send a message
lock = CountDownLatch(1)
roomFromBobPOV.sendTextMessage(messagesFromBob[1]) roomFromBobPOV.sendTextMessage(messagesFromBob[1])
// android does not echo the messages sent from itself
// messagesReceivedByBobCount++
mTestHelper.await(lock)
// assertEquals(3, messagesReceivedByBobCount)
// Bob send a message
lock = CountDownLatch(1)
roomFromBobPOV.sendTextMessage(messagesFromBob[2]) roomFromBobPOV.sendTextMessage(messagesFromBob[2])
// android does not echo the messages sent from itself
// messagesReceivedByBobCount++
mTestHelper.await(lock)
// assertEquals(4, messagesReceivedByBobCount)
// Alice sends a message // Alice sends a message
lock = CountDownLatch(2)
roomFromAlicePOV.sendTextMessage(messagesFromAlice[1]) roomFromAlicePOV.sendTextMessage(messagesFromAlice[1])
mTestHelper.await(lock) mTestHelper.await(lock)
// assertEquals(5, messagesReceivedByBobCount)
bobTimeline.removeListener(bobEventsListener) bobTimeline.removeListener(bobEventsListener)
bobTimeline.dispose()
return cryptoTestData return cryptoTestData
} }
@ -340,18 +289,14 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData { fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
return MegolmBackupAuthData( return MegolmBackupAuthData(
publicKey = "abcdefg", publicKey = "abcdefg",
signatures = HashMap<String, Map<String, String>>().apply { signatures = mapOf("something" to mapOf("ed25519:something" to "hijklmnop"))
this["something"] = HashMap<String, String>().apply {
this["ed25519:something"] = "hijklmnop"
}
}
) )
} }
fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo { fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo {
return MegolmBackupCreationInfo().apply { return MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = createFakeMegolmBackupAuthData() authData = createFakeMegolmBackupAuthData()
} )
} }
} }

View File

@ -22,11 +22,11 @@ object TestConstants {
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
// Time out to use when waiting for server response. 60s // Time out to use when waiting for server response. 10s
private const val AWAIT_TIME_OUT_MILLIS = 60000 private const val AWAIT_TIME_OUT_MILLIS = 10_000
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes // Time out to use when waiting for server response, when the debugger is connected. 10 minutes
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60000 private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000
const val USER_ALICE = "Alice" const val USER_ALICE = "Alice"
const val USER_BOB = "Bob" const val USER_BOB = "Bob"

View File

@ -2,12 +2,10 @@ package im.vector.matrix.android.internal.crypto.crosssigning
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.SessionTestParams import im.vector.matrix.android.common.SessionTestParams
import im.vector.matrix.android.common.TestConstants import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.common.TestMatrixCallback
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
@ -21,7 +19,6 @@ import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ -34,14 +31,13 @@ class XSigningTest : InstrumentedTest {
fun test_InitializeAndStoreKeys() { fun test_InitializeAndStoreKeys() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceLatch = CountDownLatch(1) mTestHelper.doSync<Unit> {
aliceSession.cryptoService().crossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(UserPasswordAuth( .initializeCrossSigning(UserPasswordAuth(
user = aliceSession.myUserId, user = aliceSession.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
), TestMatrixCallback(aliceLatch)) ), it)
}
mTestHelper.await(aliceLatch)
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys() val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
val masterPubKey = myCrossSigningKeys?.masterKey() val masterPubKey = myCrossSigningKeys?.masterKey()
@ -55,7 +51,7 @@ class XSigningTest : InstrumentedTest {
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified()) assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
@Test @Test
@ -74,17 +70,11 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
val latch = CountDownLatch(2) mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
mTestHelper.await(latch)
// Check that alice can see bob keys // Check that alice can see bob keys
val downloadLatch = CountDownLatch(1) mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch))
mTestHelper.await(downloadLatch)
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId) val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey()) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
@ -96,8 +86,8 @@ class XSigningTest : InstrumentedTest {
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signout(bobSession) mTestHelper.signOutAndClose(bobSession)
} }
@Test @Test
@ -116,94 +106,56 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
val latch = CountDownLatch(2) mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
mTestHelper.await(latch)
// Check that alice can see bob keys // Check that alice can see bob keys
val downloadLatch = CountDownLatch(1)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch)) mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
mTestHelper.await(downloadLatch)
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId) val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false) assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
val trustLatch = CountDownLatch(1) mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
trustLatch.countDown()
}
override fun onFailure(failure: Throwable) {
fail("Failed to trust bob")
}
})
mTestHelper.await(trustLatch)
// Now bobs logs in on a new device and verifies it // Now bobs logs in on a new device and verifies it
// We will want to test that in alice POV, this new device would be trusted by cross signing // We will want to test that in alice POV, this new device would be trusted by cross signing
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true)) val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!!
// Check that bob first session sees the new login // Check that bob first session sees the new login
val bobKeysLatch = CountDownLatch(1) val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
override fun onFailure(failure: Throwable) { }
fail("Failed to get device")
}
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) { if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) { fail("Bob should see the new device")
fail("Bob should see the new device") }
}
bobKeysLatch.countDown()
}
})
mTestHelper.await(bobKeysLatch)
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId) val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session // Manually mark it as trusted from first session
val bobSignLatch = CountDownLatch(1) mTestHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback<Unit> { bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
override fun onSuccess(data: Unit) { }
bobSignLatch.countDown()
}
override fun onFailure(failure: Throwable) {
fail("Failed to trust bob ${failure.localizedMessage}")
}
})
mTestHelper.await(bobSignLatch)
// Now alice should cross trust bob's second device // Now alice should cross trust bob's second device
val aliceKeysLatch = CountDownLatch(1) val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
override fun onFailure(failure: Throwable) { }
fail("Failed to get device")
}
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) { // check that the device is seen
// check that the device is seen if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { fail("Alice should see the new device")
fail("Alice should see the new device") }
}
aliceKeysLatch.countDown()
}
})
mTestHelper.await(aliceKeysLatch)
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null) val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified()) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signout(bobSession) mTestHelper.signOutAndClose(bobSession)
mTestHelper.signout(bobSession2) mTestHelper.signOutAndClose(bobSession2)
} }
} }

View File

@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.keysbackup
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.listeners.StepProgressListener
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
@ -58,7 +57,7 @@ import java.util.Collections
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.JVM)
class KeysBackupTest : InstrumentedTest { class KeysBackupTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context()) private val mTestHelper = CommonTestHelper(context())
@ -103,6 +102,8 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(sessionsCount, sessions3.size) assertEquals(sessionsCount, sessions3.size)
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -120,31 +121,18 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val latch = CountDownLatch(1) val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(null, null, it)
}
keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback<MegolmBackupCreationInfo> { assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
override fun onSuccess(data: MegolmBackupCreationInfo) { assertNotNull(megolmBackupCreationInfo.authData)
assertNotNull(data) assertNotNull(megolmBackupCreationInfo.authData!!.publicKey)
assertNotNull(megolmBackupCreationInfo.authData!!.signatures)
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, data.algorithm) assertNotNull(megolmBackupCreationInfo.recoveryKey)
assertNotNull(data.authData)
assertNotNull(data.authData!!.publicKey)
assertNotNull(data.authData!!.signatures)
assertNotNull(data.recoveryKey)
latch.countDown()
}
override fun onFailure(failure: Throwable) {
fail(failure.localizedMessage)
latch.countDown()
}
})
mTestHelper.await(latch)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
bobSession.close() mTestHelper.signOutAndClose(bobSession)
} }
/** /**
@ -160,45 +148,22 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
val latch = CountDownLatch(1) keysBackup.prepareKeysBackupVersion(null, null, it)
keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback<MegolmBackupCreationInfo> { }
override fun onSuccess(data: MegolmBackupCreationInfo) {
megolmBackupCreationInfo = data
latch.countDown()
}
override fun onFailure(failure: Throwable) {
fail(failure.localizedMessage)
latch.countDown()
}
})
mTestHelper.await(latch)
assertNotNull(megolmBackupCreationInfo)
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val latch2 = CountDownLatch(1)
// Create the version // Create the version
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback<KeysVersion>(latch2) { mTestHelper.doSync<KeysVersion> {
override fun onSuccess(data: KeysVersion) { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
assertNotNull(data) }
assertNotNull(data.version)
super.onSuccess(data)
}
})
mTestHelper.await(latch2)
// Backup must be enable now // Backup must be enable now
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
bobSession.close() mTestHelper.signOutAndClose(bobSession)
} }
/** /**
@ -238,7 +203,7 @@ class KeysBackupTest : InstrumentedTest {
KeysBackupState.ReadyToBackUp KeysBackupState.ReadyToBackUp
) )
) )
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -259,18 +224,17 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, nbOfKeys) assertEquals(2, nbOfKeys)
val latch = CountDownLatch(1)
var lastBackedUpKeysProgress = 0 var lastBackedUpKeysProgress = 0
keysBackup.backupAllGroupSessions(object : ProgressListener { mTestHelper.doSync<Unit> {
override fun onProgress(progress: Int, total: Int) { keysBackup.backupAllGroupSessions(object : ProgressListener {
assertEquals(nbOfKeys, total) override fun onProgress(progress: Int, total: Int) {
lastBackedUpKeysProgress = progress assertEquals(nbOfKeys, total)
} lastBackedUpKeysProgress = progress
}, TestMatrixCallback(latch)) }
}, it)
}
mTestHelper.await(latch)
assertEquals(nbOfKeys, lastBackedUpKeysProgress) assertEquals(nbOfKeys, lastBackedUpKeysProgress)
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
@ -278,7 +242,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -321,7 +285,7 @@ class KeysBackupTest : InstrumentedTest {
assertKeysEquals(session.exportKeys(), sessionData) assertKeysEquals(session.exportKeys(), sessionData)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -335,25 +299,19 @@ class KeysBackupTest : InstrumentedTest {
val testData = createKeysBackupScenarioWithPassword(null) val testData = createKeysBackupScenarioWithPassword(null)
// - Restore the e2e backup from the homeserver // - Restore the e2e backup from the homeserver
val latch2 = CountDownLatch(1) val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
var importRoomKeysResult: ImportRoomKeysResult? = null testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null,
null, null,
null, null,
null, it
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) { )
override fun onSuccess(data: ImportRoomKeysResult) { }
importRoomKeysResult = data
super.onSuccess(data)
}
}
)
mTestHelper.await(latch2)
checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -370,6 +328,8 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun restoreKeysBackupAndKeyShareRequestTest() { fun restoreKeysBackupAndKeyShareRequestTest() {
fail("Check with Valere for this test. I think we do not send key share request")
val testData = createKeysBackupScenarioWithPassword(null) val testData = createKeysBackupScenarioWithPassword(null)
// - Check the SDK sent key share requests // - Check the SDK sent key share requests
@ -383,23 +343,17 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(unsentRequest != null || sentRequest != null) assertTrue(unsentRequest != null || sentRequest != null)
// - Restore the e2e backup from the homeserver // - Restore the e2e backup from the homeserver
val latch2 = CountDownLatch(1) val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
var importRoomKeysResult: ImportRoomKeysResult? = null testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null,
null, null,
null, null,
null, it
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) { )
override fun onSuccess(data: ImportRoomKeysResult) { }
importRoomKeysResult = data
super.onSuccess(data)
}
}
)
mTestHelper.await(latch2)
checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
// - There must be no more pending key share requests // - There must be no more pending key share requests
val unsentRequestAfterRestoration = cryptoStore2 val unsentRequestAfterRestoration = cryptoStore2
@ -410,7 +364,7 @@ class KeysBackupTest : InstrumentedTest {
// Request is either sent or unsent // Request is either sent or unsent
assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -437,13 +391,13 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device // - Trust the backup from the new device
val latch = CountDownLatch(1) mTestHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
true, true,
TestMatrixCallback(latch) it
) )
mTestHelper.await(latch) }
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -453,38 +407,23 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server // - Retrieve the last version from the server
val latch2 = CountDownLatch(1) val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
var keysVersionResult: KeysVersionResult? = null testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion( }
object : TestMatrixCallback<KeysVersionResult?>(latch2) {
override fun onSuccess(data: KeysVersionResult?) {
keysVersionResult = data
super.onSuccess(data)
}
}
)
mTestHelper.await(latch2)
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val latch3 = CountDownLatch(1) val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
var keysBackupVersionTrust: KeysBackupVersionTrust? = null testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!, }
object : TestMatrixCallback<KeysBackupVersionTrust>(latch3) {
override fun onSuccess(data: KeysBackupVersionTrust) {
keysBackupVersionTrust = data
super.onSuccess(data)
}
})
mTestHelper.await(latch3)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust!!.usable) assertTrue(keysBackupVersionTrust.usable)
assertEquals(2, keysBackupVersionTrust!!.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -511,13 +450,13 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device with the recovery key // - Trust the backup from the new device with the recovery key
val latch = CountDownLatch(1) mTestHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
TestMatrixCallback(latch) it
) )
mTestHelper.await(latch) }
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -527,38 +466,23 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server // - Retrieve the last version from the server
val latch2 = CountDownLatch(1) val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
var keysVersionResult: KeysVersionResult? = null testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion( }
object : TestMatrixCallback<KeysVersionResult?>(latch2) {
override fun onSuccess(data: KeysVersionResult?) {
keysVersionResult = data
super.onSuccess(data)
}
}
)
mTestHelper.await(latch2)
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val latch3 = CountDownLatch(1) val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
var keysBackupVersionTrust: KeysBackupVersionTrust? = null testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!, }
object : TestMatrixCallback<KeysBackupVersionTrust>(latch3) {
override fun onSuccess(data: KeysBackupVersionTrust) {
keysBackupVersionTrust = data
super.onSuccess(data)
}
})
mTestHelper.await(latch3)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust!!.usable) assertTrue(keysBackupVersionTrust.usable)
assertEquals(2, keysBackupVersionTrust!!.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -597,7 +521,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -626,13 +550,13 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device with the password // - Trust the backup from the new device with the password
val latch = CountDownLatch(1) mTestHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password, password,
TestMatrixCallback(latch) it
) )
mTestHelper.await(latch) }
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -642,38 +566,23 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server // - Retrieve the last version from the server
val latch2 = CountDownLatch(1) val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
var keysVersionResult: KeysVersionResult? = null testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion( }
object : TestMatrixCallback<KeysVersionResult?>(latch2) {
override fun onSuccess(data: KeysVersionResult?) {
keysVersionResult = data
super.onSuccess(data)
}
}
)
mTestHelper.await(latch2)
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val latch3 = CountDownLatch(1) val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
var keysBackupVersionTrust: KeysBackupVersionTrust? = null testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!, }
object : TestMatrixCallback<KeysBackupVersionTrust>(latch3) {
override fun onSuccess(data: KeysBackupVersionTrust) {
keysBackupVersionTrust = data
super.onSuccess(data)
}
})
mTestHelper.await(latch3)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust!!.usable) assertTrue(keysBackupVersionTrust.usable)
assertEquals(2, keysBackupVersionTrust!!.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -715,7 +624,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -748,7 +657,7 @@ class KeysBackupTest : InstrumentedTest {
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -764,27 +673,21 @@ class KeysBackupTest : InstrumentedTest {
val testData = createKeysBackupScenarioWithPassword(password) val testData = createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the password // - Restore the e2e backup with the password
val latch2 = CountDownLatch(1)
var importRoomKeysResult: ImportRoomKeysResult? = null
val steps = ArrayList<StepProgressListener.Step>() val steps = ArrayList<StepProgressListener.Step>()
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
password, testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
null, password,
null, null,
object : StepProgressListener { null,
override fun onStepProgress(step: StepProgressListener.Step) { object : StepProgressListener {
steps.add(step) override fun onStepProgress(step: StepProgressListener.Step) {
} steps.add(step)
}, }
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) { },
override fun onSuccess(data: ImportRoomKeysResult) { it
importRoomKeysResult = data )
super.onSuccess(data) }
}
}
)
mTestHelper.await(latch2)
// Check steps // Check steps
assertEquals(105, steps.size) assertEquals(105, steps.size)
@ -807,9 +710,9 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress) assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -845,7 +748,7 @@ class KeysBackupTest : InstrumentedTest {
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -861,25 +764,19 @@ class KeysBackupTest : InstrumentedTest {
val testData = createKeysBackupScenarioWithPassword(password) val testData = createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the recovery key. // - Restore the e2e backup with the recovery key.
val latch2 = CountDownLatch(1) val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
var importRoomKeysResult: ImportRoomKeysResult? = null testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null,
null, null,
null, null,
null, it
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) { )
override fun onSuccess(data: ImportRoomKeysResult) { }
importRoomKeysResult = data
super.onSuccess(data)
}
}
)
mTestHelper.await(latch2)
checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -912,7 +809,7 @@ class KeysBackupTest : InstrumentedTest {
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cryptoTestData.close() testData.cleanUp(mTestHelper)
} }
/** /**
@ -932,46 +829,27 @@ class KeysBackupTest : InstrumentedTest {
prepareAndCreateKeysBackupData(keysBackup) prepareAndCreateKeysBackupData(keysBackup)
// Get key backup version from the home server // Get key backup version from the home server
var keysVersionResult: KeysVersionResult? = null val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
val lock = CountDownLatch(1) keysBackup.getCurrentVersion(it)
keysBackup.getCurrentVersion(object : TestMatrixCallback<KeysVersionResult?>(lock) { }
override fun onSuccess(data: KeysVersionResult?) {
keysVersionResult = data
super.onSuccess(data)
}
})
mTestHelper.await(lock)
assertNotNull(keysVersionResult)
// - Check the returned KeyBackupVersion is trusted // - Check the returned KeyBackupVersion is trusted
val latch = CountDownLatch(1) val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
var keysBackupVersionTrust: KeysBackupVersionTrust? = null keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
keysBackup.getKeysBackupTrust(keysVersionResult!!, object : MatrixCallback<KeysBackupVersionTrust> { }
override fun onSuccess(data: KeysBackupVersionTrust) {
keysBackupVersionTrust = data
latch.countDown()
}
override fun onFailure(failure: Throwable) {
super.onFailure(failure)
latch.countDown()
}
})
mTestHelper.await(latch)
assertNotNull(keysBackupVersionTrust) assertNotNull(keysBackupVersionTrust)
assertTrue(keysBackupVersionTrust!!.usable) assertTrue(keysBackupVersionTrust.usable)
assertEquals(1, keysBackupVersionTrust!!.signatures.size) assertEquals(1, keysBackupVersionTrust.signatures.size)
val signature = keysBackupVersionTrust!!.signatures[0] val signature = keysBackupVersionTrust.signatures[0]
assertTrue(signature.valid) assertTrue(signature.valid)
assertNotNull(signature.device) assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -983,6 +861,7 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
fail("This test still fail. To investigate")
// - Create a backup version // - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
@ -1000,7 +879,7 @@ class KeysBackupTest : InstrumentedTest {
// - Log Alice on a new device // - Log Alice on a new device
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync) val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
val keysBackup2 = aliceSession2.cryptoService().keysBackupService() val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
@ -1012,12 +891,12 @@ class KeysBackupTest : InstrumentedTest {
keysBackup2.addListener(object : KeysBackupStateListener { keysBackup2.addListener(object : KeysBackupStateListener {
override fun onStateChange(newState: KeysBackupState) { override fun onStateChange(newState: KeysBackupState) {
// Check the backup completes // Check the backup completes
if (keysBackup.state == KeysBackupState.ReadyToBackUp) { if (newState == KeysBackupState.ReadyToBackUp) {
count++ count++
if (count == 2) { if (count == 2) {
// Remove itself from the list of listeners // Remove itself from the list of listeners
keysBackup.removeListener(this) keysBackup2.removeListener(this)
latch.countDown() latch.countDown()
} }
@ -1030,7 +909,7 @@ class KeysBackupTest : InstrumentedTest {
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null)
aliceSession2.close() mTestHelper.signOutAndClose(aliceSession2)
} }
/** /**
@ -1079,21 +958,17 @@ class KeysBackupTest : InstrumentedTest {
mTestHelper.await(latch0) mTestHelper.await(latch0)
// - Create a new backup with fake data on the homeserver, directly using the rest client // - Create a new backup with fake data on the homeserver, directly using the rest client
val latch = CountDownLatch(1)
val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo()
(keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, TestMatrixCallback(latch)) mTestHelper.doSync<KeysVersion> {
mTestHelper.await(latch) (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it)
}
// Reset the store backup status for keys // Reset the store backup status for keys
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers() (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
// - Make alice back up all her keys again // - Make alice back up all her keys again
val latch2 = CountDownLatch(1) val latch2 = CountDownLatch(1)
keysBackup.backupAllGroupSessions(object : ProgressListener { keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false))
override fun onProgress(progress: Int, total: Int) {
}
}, TestMatrixCallback(latch2, false))
mTestHelper.await(latch2) mTestHelper.await(latch2)
// -> That must fail and her backup state must be WrongBackUpVersion // -> That must fail and her backup state must be WrongBackUpVersion
@ -1101,7 +976,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -1129,20 +1004,14 @@ class KeysBackupTest : InstrumentedTest {
prepareAndCreateKeysBackupData(keysBackup) prepareAndCreateKeysBackupData(keysBackup)
// Wait for keys backup to finish by asking again to backup keys. // Wait for keys backup to finish by asking again to backup keys.
val latch = CountDownLatch(1) mTestHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener { keysBackup.backupAllGroupSessions(null, it)
override fun onProgress(progress: Int, total: Int) { }
}
}, TestMatrixCallback(latch))
mTestHelper.await(latch)
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!! val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
val oldKeyBackupVersion = keysBackup.currentBackupVersion val oldKeyBackupVersion = keysBackup.currentBackupVersion
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
// Close first Alice session, else they will share the same Crypto store and the test fails.
cryptoTestData.firstSession.close()
// - Log Alice on a new device // - Log Alice on a new device
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
@ -1160,15 +1029,14 @@ class KeysBackupTest : InstrumentedTest {
var isSuccessful = false var isSuccessful = false
val latch2 = CountDownLatch(1) val latch2 = CountDownLatch(1)
keysBackup2.backupAllGroupSessions(object : ProgressListener { keysBackup2.backupAllGroupSessions(
override fun onProgress(progress: Int, total: Int) { null,
} object : TestMatrixCallback<Unit>(latch2, false) {
}, object : TestMatrixCallback<Unit>(latch2, false) { override fun onSuccess(data: Unit) {
override fun onSuccess(data: Unit) { isSuccessful = true
isSuccessful = true super.onSuccess(data)
super.onSuccess(data) }
} })
})
mTestHelper.await(latch2) mTestHelper.await(latch2)
assertFalse(isSuccessful) assertFalse(isSuccessful)
@ -1178,7 +1046,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup2.isEnabled) assertFalse(keysBackup2.isEnabled)
// - Validate the old device from the new one // - Validate the old device from the new one
aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(false, true), aliceSession2.myUserId, oldDeviceId) aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId)
// -> Backup should automatically enable on the new device // -> Backup should automatically enable on the new device
val latch4 = CountDownLatch(1) val latch4 = CountDownLatch(1)
@ -1198,17 +1066,17 @@ class KeysBackupTest : InstrumentedTest {
// -> It must use the same backup version // -> It must use the same backup version
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
val latch5 = CountDownLatch(1) mTestHelper.doSync<Unit> {
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, TestMatrixCallback(latch5)) aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
mTestHelper.await(latch5) }
// -> It must success // -> It must success
assertTrue(aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(aliceSession2.cryptoService().keysBackupService().isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null)
aliceSession2.close() mTestHelper.signOutAndClose(aliceSession2)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -1230,18 +1098,14 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
val latch = CountDownLatch(1)
// Delete the backup // Delete the backup
keysBackup.deleteBackup(keyBackupCreationInfo.version, TestMatrixCallback(latch)) mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
mTestHelper.await(latch)
// Backup is now disabled // Backup is now disabled
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/* ========================================================================================== /* ==========================================================================================
@ -1280,49 +1144,26 @@ class KeysBackupTest : InstrumentedTest {
password: String? = null): PrepareKeysBackupDataResult { password: String? = null): PrepareKeysBackupDataResult {
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
val latch = CountDownLatch(1) keysBackup.prepareKeysBackupVersion(password, null, it)
keysBackup.prepareKeysBackupVersion(password, null, object : MatrixCallback<MegolmBackupCreationInfo> { }
override fun onSuccess(data: MegolmBackupCreationInfo) {
megolmBackupCreationInfo = data
latch.countDown()
}
override fun onFailure(failure: Throwable) {
fail(failure.localizedMessage)
latch.countDown()
}
})
mTestHelper.await(latch)
assertNotNull(megolmBackupCreationInfo) assertNotNull(megolmBackupCreationInfo)
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val latch2 = CountDownLatch(1)
// Create the version // Create the version
var version: String? = null val keysVersion = mTestHelper.doSync<KeysVersion> {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback<KeysVersion>(latch2) { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
override fun onSuccess(data: KeysVersion) { }
assertNotNull(data)
assertNotNull(data.version)
version = data.version assertNotNull(keysVersion.version)
super.onSuccess(data)
}
})
mTestHelper.await(latch2)
// Backup must be enable now // Backup must be enable now
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
assertNotNull(version)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
return PrepareKeysBackupDataResult(megolmBackupCreationInfo!!, version!!) return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
} }
private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
@ -1347,7 +1188,12 @@ class KeysBackupTest : InstrumentedTest {
private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData, private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
val aliceKeys: List<OlmInboundGroupSessionWrapper>, val aliceKeys: List<OlmInboundGroupSessionWrapper>,
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
val aliceSession2: Session) val aliceSession2: Session) {
fun cleanUp(testHelper: CommonTestHelper) {
cryptoTestData.cleanUp(testHelper)
testHelper.signOutAndClose(aliceSession2)
}
}
/** /**
* Common initial condition * Common initial condition
@ -1369,27 +1215,22 @@ class KeysBackupTest : InstrumentedTest {
// - Do an e2e backup to the homeserver // - Do an e2e backup to the homeserver
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
val latch = CountDownLatch(1)
var lastProgress = 0 var lastProgress = 0
var lastTotal = 0 var lastTotal = 0
keysBackup.backupAllGroupSessions(object : ProgressListener { mTestHelper.doSync<Unit> {
override fun onProgress(progress: Int, total: Int) { keysBackup.backupAllGroupSessions(object : ProgressListener {
lastProgress = progress override fun onProgress(progress: Int, total: Int) {
lastTotal = total lastProgress = progress
} lastTotal = total
}, TestMatrixCallback(latch)) }
mTestHelper.await(latch) }, it)
}
assertEquals(2, lastProgress) assertEquals(2, lastProgress)
assertEquals(2, lastTotal) assertEquals(2, lastTotal)
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
// Logout first Alice session, else they will share the same Crypto store and some tests may fail.
val latch2 = CountDownLatch(1)
cryptoTestData.firstSession.signOut(true, TestMatrixCallback(latch2))
mTestHelper.await(latch2)
// - Log Alice on a new device // - Log Alice on a new device
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)

View File

@ -16,26 +16,25 @@
package im.vector.matrix.android.internal.crypto.ssss package im.vector.matrix.android.internal.crypto.ssss
import android.util.Base64
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
import im.vector.matrix.android.api.session.securestorage.KeySigner import im.vector.matrix.android.api.session.securestorage.KeySigner
import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.SessionTestParams import im.vector.matrix.android.common.SessionTestParams
import im.vector.matrix.android.common.TestConstants import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.common.TestMatrixCallback import im.vector.matrix.android.common.TestMatrixCallback
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -71,7 +70,7 @@ class QuadSTests : InstrumentedTest {
val TEST_KEY_ID = "my.test.Key" val TEST_KEY_ID = "my.test.Key"
val ssssKeyCreationInfo = mTestHelper.doSync<SsssKeyCreationInfo> { mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it) quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
} }
@ -95,16 +94,9 @@ class QuadSTests : InstrumentedTest {
assertNotNull("Key should be stored in account data", accountData) assertNotNull("Key should be stored in account data", accountData)
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content) val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
assertNotNull("Key Content cannot be parsed", parsed) assertNotNull("Key Content cannot be parsed", parsed)
assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm) assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_AES_HMAC_SHA2, parsed!!.algorithm)
assertEquals("Unexpected key name", "Test Key", parsed.name) assertEquals("Unexpected key name", "Test Key", parsed.name)
assertNull("Key was not generated from passphrase", parsed.passphrase) assertNull("Key was not generated from passphrase", parsed.passphrase)
assertNotNull("Pubkey should be defined", parsed.publicKey)
val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(ssssKeyCreationInfo.recoveryKey)
val pubKey = withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey)
}
assertEquals("Unexpected Public Key", pubKey, parsed.publicKey)
// Set as default key // Set as default key
quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {}) quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {})
@ -128,7 +120,7 @@ class QuadSTests : InstrumentedTest {
assertNotNull(defaultKeyAccountData?.content) assertNotNull(defaultKeyAccountData?.content)
assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key")) assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
@Test @Test
@ -137,13 +129,15 @@ class QuadSTests : InstrumentedTest {
val keyId = "My.Key" val keyId = "My.Key"
val info = generatedSecret(aliceSession, keyId, true) val info = generatedSecret(aliceSession, keyId, true)
val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey)
// Store a secret // Store a secret
val clearSecret = Base64.encodeToString("42".toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP) val clearSecret = "42".toByteArray().toBase64NoPadding()
mTestHelper.doSync<Unit> { mTestHelper.doSync<Unit> {
aliceSession.sharedSecretStorageService.storeSecret( aliceSession.sharedSecretStorageService.storeSecret(
"secret.of.life", "secret.of.life",
clearSecret, clearSecret,
null, // default key listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key
it it
) )
} }
@ -157,14 +151,13 @@ class QuadSTests : InstrumentedTest {
val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId)) val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId))
assertNotNull(secret?.ciphertext) assertNotNull(secret?.ciphertext)
assertNotNull(secret?.mac) assertNotNull(secret?.mac)
assertNotNull(secret?.ephemeral) assertNotNull(secret?.initializationVector)
// Try to decrypt?? // Try to decrypt??
val keySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(info.recoveryKey)
val decryptedSecret = mTestHelper.doSync<String> { val decryptedSecret = mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("secret.of.life", aliceSession.sharedSecretStorageService.getSecret(
"secret.of.life",
null, // default key null, // default key
keySpec!!, keySpec!!,
it it
@ -172,7 +165,7 @@ class QuadSTests : InstrumentedTest {
} }
assertEquals("Secret mismatch", clearSecret, decryptedSecret) assertEquals("Secret mismatch", clearSecret, decryptedSecret)
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
@Test @Test
@ -192,7 +185,7 @@ class QuadSTests : InstrumentedTest {
quadS.setDefaultKey(TEST_KEY_ID, it) quadS.setDefaultKey(TEST_KEY_ID, it)
} }
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
@Test @Test
@ -209,7 +202,10 @@ class QuadSTests : InstrumentedTest {
aliceSession.sharedSecretStorageService.storeSecret( aliceSession.sharedSecretStorageService.storeSecret(
"my.secret", "my.secret",
mySecretText.toByteArray().toBase64NoPadding(), mySecretText.toByteArray().toBase64NoPadding(),
listOf(keyId1, keyId2), listOf(
SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey))
),
it it
) )
} }
@ -226,7 +222,7 @@ class QuadSTests : InstrumentedTest {
mTestHelper.doSync<String> { mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1, keyId1,
Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
it it
) )
} }
@ -234,12 +230,12 @@ class QuadSTests : InstrumentedTest {
mTestHelper.doSync<String> { mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId2, keyId2,
Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
it it
) )
} }
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
@Test @Test
@ -255,7 +251,7 @@ class QuadSTests : InstrumentedTest {
aliceSession.sharedSecretStorageService.storeSecret( aliceSession.sharedSecretStorageService.storeSecret(
"my.secret", "my.secret",
mySecretText.toByteArray().toBase64NoPadding(), mySecretText.toByteArray().toBase64NoPadding(),
listOf(keyId1), listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))),
it it
) )
} }
@ -264,7 +260,7 @@ class QuadSTests : InstrumentedTest {
var error = false var error = false
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1, keyId1,
Curve25519AesSha2KeySpec.fromPassphrase( RawBytesKeySpec.fromPassphrase(
"A bad passphrase", "A bad passphrase",
key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.salt ?: "",
key1Info.content?.passphrase?.iterations ?: 0, key1Info.content?.passphrase?.iterations ?: 0,
@ -289,7 +285,7 @@ class QuadSTests : InstrumentedTest {
mTestHelper.doSync<String> { mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1, keyId1,
Curve25519AesSha2KeySpec.fromPassphrase( RawBytesKeySpec.fromPassphrase(
passphrase, passphrase,
key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.salt ?: "",
key1Info.content?.passphrase?.iterations ?: 0, key1Info.content?.passphrase?.iterations ?: 0,
@ -298,7 +294,7 @@ class QuadSTests : InstrumentedTest {
) )
} }
mTestHelper.signout(aliceSession) mTestHelper.signOutAndClose(aliceSession)
} }
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {

View File

@ -132,7 +132,7 @@ class SASTest : InstrumentedTest {
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -189,7 +189,7 @@ class SASTest : InstrumentedTest {
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -227,7 +227,7 @@ class SASTest : InstrumentedTest {
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -265,7 +265,7 @@ class SASTest : InstrumentedTest {
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
private fun fakeBobStart(bobSession: Session, private fun fakeBobStart(bobSession: Session,
@ -334,7 +334,7 @@ class SASTest : InstrumentedTest {
mTestHelper.await(aliceCreatedLatch) mTestHelper.await(aliceCreatedLatch)
mTestHelper.await(aliceCancelledLatch) mTestHelper.await(aliceCancelledLatch)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -393,7 +393,7 @@ class SASTest : InstrumentedTest {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it)) assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
} }
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -449,7 +449,7 @@ class SASTest : InstrumentedTest {
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -514,6 +514,6 @@ class SASTest : InstrumentedTest {
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
} }

View File

@ -227,6 +227,6 @@ class VerificationTest : InstrumentedTest {
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
} }
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
} }

View File

@ -46,13 +46,13 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class WellKnown( data class WellKnown(
@Json(name = "m.homeserver") @Json(name = "m.homeserver")
var homeServer: WellKnownBaseConfig? = null, val homeServer: WellKnownBaseConfig? = null,
@Json(name = "m.identity_server") @Json(name = "m.identity_server")
var identityServer: WellKnownBaseConfig? = null, val identityServer: WellKnownBaseConfig? = null,
@Json(name = "m.integrations") @Json(name = "m.integrations")
var integrations: Map<String, @JvmSuppressWildcards Any>? = null val integrations: Map<String, @JvmSuppressWildcards Any>? = null
) { ) {
/** /**
* Returns the list of integration managers proposed * Returns the list of integration managers proposed

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.api.extensions package im.vector.matrix.android.api.extensions
import im.vector.matrix.android.api.comparators.DatedObjectComparators
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
@ -33,7 +32,5 @@ fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint()
* ========================================================================================== */ * ========================================================================================== */
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> { fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
val list = toMutableList() return this.sortedByDescending { it.lastSeenTs ?: 0 }
list.sortWith(DatedObjectComparators.descComparator)
return list
} }

View File

@ -42,6 +42,10 @@ interface CrossSigningService {
fun initializeCrossSigning(authParams: UserPasswordAuth?, fun initializeCrossSigning(authParams: UserPasswordAuth?,
callback: MatrixCallback<Unit>? = null) callback: MatrixCallback<Unit>? = null)
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?,
sskPrivateKey: String?) : UserTrustResult
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
@ -53,11 +57,13 @@ interface CrossSigningService {
fun trustUser(otherUserId: String, fun trustUser(otherUserId: String,
callback: MatrixCallback<Unit>) callback: MatrixCallback<Unit>)
fun markMyMasterKeyAsTrusted()
/** /**
* Sign one of your devices and upload the signature * Sign one of your devices and upload the signature
*/ */
fun signDevice(deviceId: String, fun trustDevice(deviceId: String,
callback: MatrixCallback<Unit>) callback: MatrixCallback<Unit>)
fun checkDeviceTrust(otherUserId: String, fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String, otherDeviceId: String,

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* 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 im.vector.matrix.android.api.session.crypto.crosssigning
const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master"
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.room.model.create package im.vector.matrix.android.api.session.room.model.create
import android.util.Patterns import android.util.Patterns
import androidx.annotation.CheckResult
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixPatterns.isUserId import im.vector.matrix.android.api.MatrixPatterns.isUserId
@ -120,37 +121,53 @@ data class CreateRoomParams(
@Json(name = "power_level_content_override") @Json(name = "power_level_content_override")
val powerLevelContentOverride: PowerLevelsContent? = null val powerLevelContentOverride: PowerLevelsContent? = null
) { ) {
/**
* Set to true means that if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
*/
@Transient @Transient
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
private set private set
fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams { /**
enableEncryptionIfInvitedUsersSupportIt = true * After calling this method, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
* @param value true to activate this behavior.
* @return this, to allow chaining methods
*/
fun enableEncryptionIfInvitedUsersSupportIt(value: Boolean = true): CreateRoomParams {
enableEncryptionIfInvitedUsersSupportIt = value
return this return this
} }
/** /**
* Add the crypto algorithm to the room creation parameters. * Add the crypto algorithm to the room creation parameters.
* *
* @param algorithm the algorithm * @param enable true to enable encryption.
* @param algorithm the algorithm, default to [MXCRYPTO_ALGORITHM_MEGOLM], which is actually the only supported algorithm for the moment
* @return a modified copy of the CreateRoomParams object, or this if there is no modification
*/ */
fun enableEncryptionWithAlgorithm(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams { @CheckResult
fun enableEncryptionWithAlgorithm(enable: Boolean = true,
algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
// Remove the existing value if any.
val newInitialStates = initialStates
?.filter { it.type != EventType.STATE_ROOM_ENCRYPTION }
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
val contentMap = mapOf("algorithm" to algorithm) if (enable) {
val contentMap = mapOf("algorithm" to algorithm)
val algoEvent = Event( val algoEvent = Event(
type = EventType.STATE_ROOM_ENCRYPTION, type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "", stateKey = "",
content = contentMap.toContent() content = contentMap.toContent()
) )
copy( copy(
initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent initialStates = newInitialStates.orEmpty() + algoEvent
) )
} else {
return copy(
initialStates = newInitialStates
)
}
} else { } else {
Timber.e("Unsupported algorithm: $algorithm") Timber.e("Unsupported algorithm: $algorithm")
this this
@ -161,7 +178,9 @@ data class CreateRoomParams(
* Force the history visibility in the room creation parameters. * Force the history visibility in the room creation parameters.
* *
* @param historyVisibility the expected history visibility, set null to remove any existing value. * @param historyVisibility the expected history visibility, set null to remove any existing value.
* @return a modified copy of the CreateRoomParams object
*/ */
@CheckResult
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams { fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
// Remove the existing value if any. // Remove the existing value if any.
val newInitialStates = initialStates val newInitialStates = initialStates
@ -187,7 +206,9 @@ data class CreateRoomParams(
/** /**
* Mark as a direct message room. * Mark as a direct message room.
* @return a modified copy of the CreateRoomParams object
*/ */
@CheckResult
fun setDirectMessage(): CreateRoomParams { fun setDirectMessage(): CreateRoomParams {
return copy( return copy(
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT, preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
@ -195,20 +216,6 @@ data class CreateRoomParams(
) )
} }
/**
* @return the invite count
*/
private fun getInviteCount(): Int {
return invitedUserIds?.size ?: 0
}
/**
* @return the pid invite count
*/
private fun getInvite3PidCount(): Int {
return invite3pids?.size ?: 0
}
/** /**
* Tells if the created room can be a direct chat one. * Tells if the created room can be a direct chat one.
* *
@ -217,7 +224,6 @@ data class CreateRoomParams(
fun isDirect(): Boolean { fun isDirect(): Boolean {
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
&& isDirect == true && isDirect == true
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
} }
/** /**
@ -232,7 +238,9 @@ data class CreateRoomParams(
* ids might be a matrix id or an email address. * ids might be a matrix id or an email address.
* *
* @param ids the participant ids to add. * @param ids the participant ids to add.
* @return a modified copy of the CreateRoomParams object
*/ */
@CheckResult
fun addParticipantIds(hsConfig: HomeServerConnectionConfig, fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
userId: String, userId: String,
ids: List<String>): CreateRoomParams { ids: List<String>): CreateRoomParams {

View File

@ -24,7 +24,7 @@ internal data class CreateRoomResponse(
/** /**
* Required. The created room's ID. * Required. The created room's ID.
*/ */
@Json(name = "room_id") var roomId: String @Json(name = "room_id") val roomId: String
) )
internal typealias JoinRoomResponse = CreateRoomResponse internal typealias JoinRoomResponse = CreateRoomResponse

View File

@ -27,5 +27,5 @@ data class PublicRoomsFilter(
* A string to search for in the room metadata, e.g. name, topic, canonical alias etc. (Optional). * A string to search for in the room metadata, e.g. name, topic, canonical alias etc. (Optional).
*/ */
@Json(name = "generic_search_term") @Json(name = "generic_search_term")
var searchTerm: String? = null val searchTerm: String? = null
) )

View File

@ -28,30 +28,30 @@ data class PublicRoomsParams(
* Limit the number of results returned. * Limit the number of results returned.
*/ */
@Json(name = "limit") @Json(name = "limit")
var limit: Int? = null, val limit: Int? = null,
/** /**
* A pagination token from a previous request, allowing clients to get the next (or previous) batch of rooms. * A pagination token from a previous request, allowing clients to get the next (or previous) batch of rooms.
* The direction of pagination is specified solely by which token is supplied, rather than via an explicit flag. * The direction of pagination is specified solely by which token is supplied, rather than via an explicit flag.
*/ */
@Json(name = "since") @Json(name = "since")
var since: String? = null, val since: String? = null,
/** /**
* Filter to apply to the results. * Filter to apply to the results.
*/ */
@Json(name = "filter") @Json(name = "filter")
var filter: PublicRoomsFilter? = null, val filter: PublicRoomsFilter? = null,
/** /**
* Whether or not to include all known networks/protocols from application services on the homeserver. Defaults to false. * Whether or not to include all known networks/protocols from application services on the homeserver. Defaults to false.
*/ */
@Json(name = "include_all_networks") @Json(name = "include_all_networks")
var includeAllNetworks: Boolean = false, val includeAllNetworks: Boolean = false,
/** /**
* The specific third party network/protocol to request from the homeserver. Can only be used if include_all_networks is false. * The specific third party network/protocol to request from the homeserver. Can only be used if include_all_networks is false.
*/ */
@Json(name = "third_party_instance_id") @Json(name = "third_party_instance_id")
var thirdPartyInstanceId: String? = null val thirdPartyInstanceId: String? = null
) )

View File

@ -26,7 +26,7 @@ data class ThirdPartyProtocol(
* where higher groupings are ordered first. For example, the name of a network should be searched before the nickname of a user. * where higher groupings are ordered first. For example, the name of a network should be searched before the nickname of a user.
*/ */
@Json(name = "user_fields") @Json(name = "user_fields")
var userFields: List<String>? = null, val userFields: List<String>? = null,
/** /**
* Required. Fields which may be used to identify a third party location. These should be ordered to suggest the way that * Required. Fields which may be used to identify a third party location. These should be ordered to suggest the way that
@ -34,15 +34,15 @@ data class ThirdPartyProtocol(
* searched before the name of a channel. * searched before the name of a channel.
*/ */
@Json(name = "location_fields") @Json(name = "location_fields")
var locationFields: List<String>? = null, val locationFields: List<String>? = null,
/** /**
* Required. A content URI representing an icon for the third party protocol. * Required. A content URI representing an icon for the third party protocol.
* *
* FIXDOC: This field was not present in legacy Riot, and it is sometimes sent by the server (no not Required?) * FIXDOC: This field was not present in legacy Riot, and it is sometimes sent by the server (so not Required?)
*/ */
@Json(name = "icon") @Json(name = "icon")
var icon: String? = null, val icon: String? = null,
/** /**
* Required. The type definitions for the fields defined in the user_fields and location_fields. Each entry in those arrays MUST have an entry here. * Required. The type definitions for the fields defined in the user_fields and location_fields. Each entry in those arrays MUST have an entry here.
@ -51,12 +51,12 @@ data class ThirdPartyProtocol(
* May be an empty object if no fields are defined. * May be an empty object if no fields are defined.
*/ */
@Json(name = "field_types") @Json(name = "field_types")
var fieldTypes: Map<String, FieldType>? = null, val fieldTypes: Map<String, FieldType>? = null,
/** /**
* Required. A list of objects representing independent instances of configuration. For example, multiple networks on IRC * Required. A list of objects representing independent instances of configuration. For example, multiple networks on IRC
* if multiple are provided by the same application service. * if multiple are provided by the same application service.
*/ */
@Json(name = "instances") @Json(name = "instances")
var instances: List<ThirdPartyProtocolInstance>? = null val instances: List<ThirdPartyProtocolInstance>? = null
) )

View File

@ -25,35 +25,35 @@ data class ThirdPartyProtocolInstance(
* Required. A human-readable description for the protocol, such as the name. * Required. A human-readable description for the protocol, such as the name.
*/ */
@Json(name = "desc") @Json(name = "desc")
var desc: String? = null, val desc: String? = null,
/** /**
* An optional content URI representing the protocol. Overrides the one provided at the higher level Protocol object. * An optional content URI representing the protocol. Overrides the one provided at the higher level Protocol object.
*/ */
@Json(name = "icon") @Json(name = "icon")
var icon: String? = null, val icon: String? = null,
/** /**
* Required. Preset values for fields the client may use to search by. * Required. Preset values for fields the client may use to search by.
*/ */
@Json(name = "fields") @Json(name = "fields")
var fields: Map<String, Any>? = null, val fields: Map<String, Any>? = null,
/** /**
* Required. A unique identifier across all instances. * Required. A unique identifier across all instances.
*/ */
@Json(name = "network_id") @Json(name = "network_id")
var networkId: String? = null, val networkId: String? = null,
/** /**
* FIXDOC Not documented on matrix.org doc * FIXDOC Not documented on matrix.org doc
*/ */
@Json(name = "instance_id") @Json(name = "instance_id")
var instanceId: String? = null, val instanceId: String? = null,
/** /**
* FIXDOC Not documented on matrix.org doc * FIXDOC Not documented on matrix.org doc
*/ */
@Json(name = "bot_user_id") @Json(name = "bot_user_id")
var botUserId: String? = null val botUserId: String? = null
) )

View File

@ -41,12 +41,12 @@ interface Timeline {
fun removeAllListeners() fun removeAllListeners()
/** /**
* This should be called before any other method after creating the timeline. It ensures the underlying database is open * This must be called before any other method after creating the timeline. It ensures the underlying database is open
*/ */
fun start() fun start()
/** /**
* This should be called when you don't need the timeline. It ensures the underlying database get closed. * This must be called when you don't need the timeline. It ensures the underlying database get closed.
*/ */
fun dispose() fun dispose()

View File

@ -27,6 +27,9 @@ interface TimelineService {
/** /**
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink. * Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
* You can also configure some settings with the [settings] param. * You can also configure some settings with the [settings] param.
*
* Important: the returned Timeline has to be started
*
* @param eventId the optional initial eventId. * @param eventId the optional initial eventId.
* @param settings settings to configure the timeline. * @param settings settings to configure the timeline.
* @return the instantiated timeline * @return the instantiated timeline

View File

@ -32,7 +32,8 @@ data class EncryptedSecretContent(
/** unpadded base64-encoded ciphertext */ /** unpadded base64-encoded ciphertext */
@Json(name = "ciphertext") val ciphertext: String? = null, @Json(name = "ciphertext") val ciphertext: String? = null,
@Json(name = "mac") val mac: String? = null, @Json(name = "mac") val mac: String? = null,
@Json(name = "ephemeral") val ephemeral: String? = null @Json(name = "ephemeral") val ephemeral: String? = null,
@Json(name = "iv") val initializationVector: String? = null
) : AccountDataContent { ) : AccountDataContent {
companion object { companion object {
/** /**

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* 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 im.vector.matrix.android.api.session.securestorage
sealed class IntegrityResult {
data class Success(val passphraseBased: Boolean) : IntegrityResult()
data class Error(val cause: SharedSecretStorageError) : IntegrityResult()
}

View File

@ -27,5 +27,6 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
object BadKeyFormat : SharedSecretStorageError("Bad Key Format") object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
object ParsingError : SharedSecretStorageError("parsing Error") object ParsingError : SharedSecretStorageError("parsing Error")
object BadMac : SharedSecretStorageError("Bad mac")
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage) data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
} }

View File

@ -42,7 +42,7 @@ interface SharedSecretStorageService {
*/ */
fun generateKey(keyId: String, fun generateKey(keyId: String,
keyName: String, keyName: String,
keySigner: KeySigner, keySigner: KeySigner?,
callback: MatrixCallback<SsssKeyCreationInfo>) callback: MatrixCallback<SsssKeyCreationInfo>)
/** /**
@ -92,7 +92,7 @@ interface SharedSecretStorageService {
* @param secret The secret contents. * @param secret The secret contents.
* @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
*/ */
fun storeSecret(name: String, secretBase64: String, keys: List<String>?, callback: MatrixCallback<Unit>) fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>, callback: MatrixCallback<Unit>)
/** /**
* Use this call to determine which SSSSKeySpec to use for requesting secret * Use this call to determine which SSSSKeySpec to use for requesting secret
@ -104,9 +104,15 @@ interface SharedSecretStorageService {
* *
* @param name The name of the secret * @param name The name of the secret
* @param keyId The id of the key that should be used to decrypt (null for default key) * @param keyId The id of the key that should be used to decrypt (null for default key)
* @param secretKey the secret key to use (@see #Curve25519AesSha2KeySpec) * @param secretKey the secret key to use (@see #RawBytesKeySpec)
* *
*/ */
@Throws
fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>) fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>)
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) : IntegrityResult
data class KeyRef(
val keyId: String?,
val keySpec: SsssKeySpec?
)
} }

View File

@ -23,14 +23,14 @@ import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyF
/** Tag class */ /** Tag class */
interface SsssKeySpec interface SsssKeySpec
data class Curve25519AesSha2KeySpec( data class RawBytesKeySpec(
val privateKey: ByteArray val privateKey: ByteArray
) : SsssKeySpec { ) : SsssKeySpec {
companion object { companion object {
fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): Curve25519AesSha2KeySpec { fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): RawBytesKeySpec {
return Curve25519AesSha2KeySpec( return RawBytesKeySpec(
privateKey = deriveKey( privateKey = deriveKey(
passphrase, passphrase,
salt, salt,
@ -40,9 +40,9 @@ data class Curve25519AesSha2KeySpec(
) )
} }
fun fromRecoveryKey(recoveryKey: String): Curve25519AesSha2KeySpec? { fun fromRecoveryKey(recoveryKey: String): RawBytesKeySpec? {
return extractCurveKeyFromRecoveryKey(recoveryKey)?.let { return extractCurveKeyFromRecoveryKey(recoveryKey)?.let {
Curve25519AesSha2KeySpec( RawBytesKeySpec(
privateKey = it privateKey = it
) )
} }
@ -53,7 +53,7 @@ data class Curve25519AesSha2KeySpec(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Curve25519AesSha2KeySpec other as RawBytesKeySpec
if (!privateKey.contentEquals(other.privateKey)) return false if (!privateKey.contentEquals(other.privateKey)) return false

View File

@ -32,20 +32,20 @@ data class RegistrationFlowResponse(
* The list of flows. * The list of flows.
*/ */
@Json(name = "flows") @Json(name = "flows")
var flows: List<InteractiveAuthenticationFlow>? = null, val flows: List<InteractiveAuthenticationFlow>? = null,
/** /**
* The list of stages the client has completed successfully. * The list of stages the client has completed successfully.
*/ */
@Json(name = "completed") @Json(name = "completed")
var completedStages: List<String>? = null, val completedStages: List<String>? = null,
/** /**
* The session identifier that the client must pass back to the home server, if one is provided, * The session identifier that the client must pass back to the home server, if one is provided,
* in subsequent attempts to authenticate in the same API call. * in subsequent attempts to authenticate in the same API call.
*/ */
@Json(name = "session") @Json(name = "session")
var session: String? = null, val session: String? = null,
/** /**
* The information that the client will need to know in order to use a given type of authentication. * The information that the client will need to know in order to use a given type of authentication.
@ -53,7 +53,7 @@ data class RegistrationFlowResponse(
* For example, the public key of reCAPTCHA stage could be given here. * For example, the public key of reCAPTCHA stage could be given here.
*/ */
@Json(name = "params") @Json(name = "params")
var params: JsonDict? = null val params: JsonDict? = null
/** /**
* WARNING, * WARNING,

View File

@ -35,6 +35,8 @@ const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-
* Secured Shared Storage algorithm constant * Secured Shared Storage algorithm constant
*/ */
const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2" const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
/* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. **/
const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2"
// TODO Refacto: use this constants everywhere // TODO Refacto: use this constants everywhere
const val ed25519 = "ed25519" const val ed25519 = "ed25519"

View File

@ -1021,12 +1021,12 @@ internal class DefaultCryptoService @Inject constructor(
return return
} }
val requestBody = RoomKeyRequestBody() val requestBody = RoomKeyRequestBody(
algorithm = wireContent["algorithm"]?.toString(),
requestBody.roomId = event.roomId roomId = event.roomId,
requestBody.algorithm = wireContent["algorithm"]?.toString() senderKey = wireContent["sender_key"]?.toString(),
requestBody.senderKey = wireContent["sender_key"]?.toString() sessionId = wireContent["session_id"]?.toString()
requestBody.sessionId = wireContent["session_id"]?.toString() )
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
} }

View File

@ -25,54 +25,56 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
/** /**
* IncomingRoomKeyRequest class defines the incoming room keys request. * IncomingRoomKeyRequest class defines the incoming room keys request.
*/ */
open class IncomingRoomKeyRequest { data class IncomingRoomKeyRequest(
/** /**
* The user id * The user id
*/ */
var userId: String? = null override val userId: String? = null,
/** /**
* The device id * The device id
*/ */
var deviceId: String? = null override val deviceId: String? = null,
/** /**
* The request id * The request id
*/ */
var requestId: String? = null override val requestId: String? = null,
/** /**
* The request body * The request body
*/ */
var requestBody: RoomKeyRequestBody? = null val requestBody: RoomKeyRequestBody? = null,
/** /**
* The runnable to call to accept to share the keys * The runnable to call to accept to share the keys
*/ */
@Transient @Transient
var share: Runnable? = null var share: Runnable? = null,
/** /**
* The runnable to call to ignore the key share request. * The runnable to call to ignore the key share request.
*/ */
@Transient @Transient
var ignore: Runnable? = null var ignore: Runnable? = null
) : IncomingRoomKeyRequestCommon {
/** companion object {
* Constructor /**
* * Factory
* @param event the event *
*/ * @param event the event
constructor(event: Event) { */
userId = event.senderId fun fromEvent(event: Event): IncomingRoomKeyRequest? {
val roomKeyShareRequest = event.getClearContent().toModel<RoomKeyShareRequest>()!! return event.getClearContent()
deviceId = roomKeyShareRequest.requestingDeviceId .toModel<RoomKeyShareRequest>()
requestId = roomKeyShareRequest.requestId ?.let {
requestBody = if (null != roomKeyShareRequest.body) roomKeyShareRequest.body else RoomKeyRequestBody() IncomingRoomKeyRequest(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId,
requestBody = it.body ?: RoomKeyRequestBody()
)
}
}
} }
/**
* Constructor for object creation from crypto store
*/
constructor()
} }

View File

@ -17,13 +17,44 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation
/** /**
* IncomingRoomKeyRequestCancellation describes the incoming room key cancellation. * IncomingRoomKeyRequestCancellation describes the incoming room key cancellation.
*/ */
class IncomingRoomKeyRequestCancellation(event: Event) : IncomingRoomKeyRequest(event) { data class IncomingRoomKeyRequestCancellation(
/**
* The user id
*/
override val userId: String? = null,
init { /**
requestBody = null * The device id
*/
override val deviceId: String? = null,
/**
* The request id
*/
override val requestId: String? = null
) : IncomingRoomKeyRequestCommon {
companion object {
/**
* Factory
*
* @param event the event
*/
fun fromEvent(event: Event): IncomingRoomKeyRequestCancellation? {
return event.getClearContent()
.toModel<RoomKeyShareCancellation>()
?.let {
IncomingRoomKeyRequestCancellation(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId
)
}
}
} }
} }

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.crypto
interface IncomingRoomKeyRequestCommon {
/**
* The user id
*/
val userId: String?
/**
* The device id
*/
val deviceId: String?
/**
* The request id
*/
val requestId: String?
}

View File

@ -53,8 +53,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
fun onRoomKeyRequestEvent(event: Event) { fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>() val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
when (roomKeyShare?.action) { when (roomKeyShare?.action) {
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event)) RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) }
RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event)) RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) }
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action ${roomKeyShare?.action}") else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action ${roomKeyShare?.action}")
} }
} }

View File

@ -28,46 +28,46 @@ data class MegolmSessionData(
* The algorithm used. * The algorithm used.
*/ */
@Json(name = "algorithm") @Json(name = "algorithm")
var algorithm: String? = null, val algorithm: String? = null,
/** /**
* Unique id for the session. * Unique id for the session.
*/ */
@Json(name = "session_id") @Json(name = "session_id")
var sessionId: String? = null, val sessionId: String? = null,
/** /**
* Sender's Curve25519 device key. * Sender's Curve25519 device key.
*/ */
@Json(name = "sender_key") @Json(name = "sender_key")
var senderKey: String? = null, val senderKey: String? = null,
/** /**
* Room this session is used in. * Room this session is used in.
*/ */
@Json(name = "room_id") @Json(name = "room_id")
var roomId: String? = null, val roomId: String? = null,
/** /**
* Base64'ed key data. * Base64'ed key data.
*/ */
@Json(name = "session_key") @Json(name = "session_key")
var sessionKey: String? = null, val sessionKey: String? = null,
/** /**
* Other keys the sender claims. * Other keys the sender claims.
*/ */
@Json(name = "sender_claimed_keys") @Json(name = "sender_claimed_keys")
var senderClaimedKeys: Map<String, String>? = null, val senderClaimedKeys: Map<String, String>? = null,
// This is a shortcut for sender_claimed_keys.get("ed25519") // This is a shortcut for sender_claimed_keys.get("ed25519")
// Keep it for compatibility reason. // Keep it for compatibility reason.
@Json(name = "sender_claimed_ed25519_key") @Json(name = "sender_claimed_ed25519_key")
var senderClaimedEd25519Key: String? = null, val senderClaimedEd25519Key: String? = null,
/** /**
* Devices which forwarded this session to us (normally empty). * Devices which forwarded this session to us (normally empty).
*/ */
@Json(name = "forwarding_curve25519_key_chain") @Json(name = "forwarding_curve25519_key_chain")
var forwardingCurve25519KeyChain: List<String>? = null val forwardingCurve25519KeyChain: List<String>? = null
) )

View File

@ -213,10 +213,11 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody
+ " from " + request.recipients + " id " + request.requestId) + " from " + request.recipients + " id " + request.requestId)
val requestMessage = RoomKeyShareRequest() val requestMessage = RoomKeyShareRequest(
requestMessage.requestingDeviceId = cryptoStore.getDeviceId() requestingDeviceId = cryptoStore.getDeviceId(),
requestMessage.requestId = request.requestId requestId = request.requestId,
requestMessage.body = request.requestBody body = request.requestBody
)
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> { sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) { private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
@ -253,9 +254,10 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
+ " to " + request.recipients + " to " + request.recipients
+ " cancellation id " + request.cancellationTxnId) + " cancellation id " + request.cancellationTxnId)
val roomKeyShareCancellation = RoomKeyShareCancellation() val roomKeyShareCancellation = RoomKeyShareCancellation(
roomKeyShareCancellation.requestingDeviceId = cryptoStore.getDeviceId() requestingDeviceId = cryptoStore.getDeviceId(),
roomKeyShareCancellation.requestId = request.cancellationTxnId requestId = request.cancellationTxnId
)
sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> { sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> {
private fun onDone() { private fun onDone() {

View File

@ -66,12 +66,12 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
totalNumbersOfImportedKeys++ totalNumbersOfImportedKeys++
// cancel any outstanding room key requests for this session // cancel any outstanding room key requests for this session
val roomKeyRequestBody = RoomKeyRequestBody() val roomKeyRequestBody = RoomKeyRequestBody(
algorithm = megolmSessionData.algorithm,
roomKeyRequestBody.algorithm = megolmSessionData.algorithm roomId = megolmSessionData.roomId,
roomKeyRequestBody.roomId = megolmSessionData.roomId senderKey = megolmSessionData.senderKey,
roomKeyRequestBody.senderKey = megolmSessionData.senderKey sessionId = megolmSessionData.sessionId
roomKeyRequestBody.sessionId = megolmSessionData.sessionId )
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody) outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody)
@ -83,7 +83,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
} }
if (progressListener != null) { if (progressListener != null) {
val progress = 100 * cpt / totalNumbersOfKeys val progress = 100 * (cpt + 1) / totalNumbersOfKeys
if (lastProgress != progress) { if (lastProgress != progress) {
lastProgress = progress lastProgress = progress

View File

@ -163,12 +163,12 @@ internal class MXMegolmDecryption(private val userId: String,
recipients.add(senderMap) recipients.add(senderMap)
} }
val requestBody = RoomKeyRequestBody() val requestBody = RoomKeyRequestBody(
roomId = event.roomId,
requestBody.roomId = event.roomId algorithm = encryptedEventContent.algorithm,
requestBody.algorithm = encryptedEventContent.algorithm senderKey = encryptedEventContent.senderKey,
requestBody.senderKey = encryptedEventContent.senderKey sessionId = encryptedEventContent.sessionId
requestBody.sessionId = encryptedEventContent.sessionId )
outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients) outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients)
} }
@ -264,12 +264,12 @@ internal class MXMegolmDecryption(private val userId: String,
if (added) { if (added) {
defaultKeysBackupService.maybeBackupKeys() defaultKeysBackupService.maybeBackupKeys()
val content = RoomKeyRequestBody() val content = RoomKeyRequestBody(
algorithm = roomKeyContent.algorithm,
content.algorithm = roomKeyContent.algorithm roomId = roomKeyContent.roomId,
content.roomId = roomKeyContent.roomId sessionId = roomKeyContent.sessionId,
content.sessionId = roomKeyContent.sessionId senderKey = senderKey
content.senderKey = senderKey )
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content) outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content)
@ -290,8 +290,8 @@ internal class MXMegolmDecryption(private val userId: String,
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
val roomId = request.requestBody?.roomId ?: return false val roomId = request.requestBody?.roomId ?: return false
val senderKey = request.requestBody?.senderKey ?: return false val senderKey = request.requestBody.senderKey ?: return false
val sessionId = request.requestBody?.sessionId ?: return false val sessionId = request.requestBody.sessionId ?: return false
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId) return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
} }
@ -319,15 +319,14 @@ internal class MXMegolmDecryption(private val userId: String,
return@mapCatching return@mapCatching
} }
Timber.v("## shareKeysWithDevice() : sharing keys for session" + Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId") " ${body.senderKey}|${body.sessionId} with device $userId:$deviceId")
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) } runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) }
.fold( .fold(
{ {
// TODO // TODO
payloadJson["content"] = it.exportKeys() payloadJson["content"] = it.exportKeys() ?: ""
?: ""
}, },
{ {
// TODO // TODO

View File

@ -20,6 +20,8 @@ import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> { internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> {
@ -29,14 +31,15 @@ internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncrypti
} }
internal class DefaultComputeTrustTask @Inject constructor( internal class DefaultComputeTrustTask @Inject constructor(
val cryptoStore: IMXCryptoStore private val cryptoStore: IMXCryptoStore,
private val coroutineDispatchers: MatrixCoroutineDispatchers
) : ComputeTrustTask { ) : ComputeTrustTask {
override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel { override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel = withContext(coroutineDispatchers.crypto) {
val allTrustedUserIds = params.userIds val allTrustedUserIds = params.userIds
.filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true } .filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true }
return if (allTrustedUserIds.isEmpty()) { if (allTrustedUserIds.isEmpty()) {
RoomEncryptionTrustLevel.Default RoomEncryptionTrustLevel.Default
} else { } else {
// If one of the verified user as an untrusted device -> warning // If one of the verified user as an untrusted device -> warning

View File

@ -88,7 +88,8 @@ internal class DefaultCrossSigningService @Inject constructor(
Timber.i("## CrossSigning - Loading master key success") Timber.i("## CrossSigning - Loading master key success")
} else { } else {
Timber.w("## CrossSigning - Public master key does not match the private key") Timber.w("## CrossSigning - Public master key does not match the private key")
// TODO untrust pkSigning.releaseSigning()
// TODO untrust?
} }
} }
privateKeysInfo.user privateKeysInfo.user
@ -100,7 +101,8 @@ internal class DefaultCrossSigningService @Inject constructor(
Timber.i("## CrossSigning - Loading User Signing key success") Timber.i("## CrossSigning - Loading User Signing key success")
} else { } else {
Timber.w("## CrossSigning - Public User key does not match the private key") Timber.w("## CrossSigning - Public User key does not match the private key")
// TODO untrust pkSigning.releaseSigning()
// TODO untrust?
} }
} }
privateKeysInfo.selfSigned privateKeysInfo.selfSigned
@ -112,7 +114,8 @@ internal class DefaultCrossSigningService @Inject constructor(
Timber.i("## CrossSigning - Loading Self Signing key success") Timber.i("## CrossSigning - Loading Self Signing key success")
} else { } else {
Timber.w("## CrossSigning - Public Self Signing key does not match the private key") Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
// TODO untrust pkSigning.releaseSigning()
// TODO untrust?
} }
} }
} }
@ -224,16 +227,18 @@ internal class DefaultCrossSigningService @Inject constructor(
val myDevice = myDeviceInfoHolder.get().myDevice val myDevice = myDeviceInfoHolder.get().myDevice
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
val signedDevice = selfSigningPkOlm.sign(canonicalJson) val signedDevice = selfSigningPkOlm.sign(canonicalJson)
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also { val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap())
it[userId] = (it[userId] .also {
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) it[userId] = (it[userId]
} ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
}
myDevice.copy(signatures = updateSignatures).let { myDevice.copy(signatures = updateSignatures).let {
uploadSignatureQueryBuilder.withDeviceInfo(it) uploadSignatureQueryBuilder.withDeviceInfo(it)
} }
// sign MSK with device key (migration) and upload signatures // sign MSK with device key (migration) and upload signatures
olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign -> val message = JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary())
olmDevice.signMessage(message)?.let { sign ->
val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap()
?: HashMap()).also { ?: HashMap()).also {
it[userId] = (it[userId] it[userId] = (it[userId]
@ -292,6 +297,80 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.clearOtherUserTrust() cryptoStore.clearOtherUserTrust()
} }
override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?,
sskPrivateKey: String?
): UserTrustResult {
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId)
var masterKeyIsTrusted = false
var userKeyIsTrusted = false
var selfSignedKeyIsTrusted = false
masterKeyPrivateKey?.fromBase64NoPadding()
?.let { privateKeySeed ->
val pkSigning = OlmPkSigning()
try {
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
masterPkSigning?.releaseSigning()
masterPkSigning = pkSigning
masterKeyIsTrusted = true
Timber.i("## CrossSigning - Loading master key success")
} else {
pkSigning.releaseSigning()
}
} catch (failure: Throwable) {
pkSigning.releaseSigning()
}
}
uskKeyPrivateKey?.fromBase64NoPadding()
?.let { privateKeySeed ->
val pkSigning = OlmPkSigning()
try {
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
userPkSigning?.releaseSigning()
userPkSigning = pkSigning
userKeyIsTrusted = true
Timber.i("## CrossSigning - Loading master key success")
} else {
pkSigning.releaseSigning()
}
} catch (failure: Throwable) {
pkSigning.releaseSigning()
}
}
sskPrivateKey?.fromBase64NoPadding()
?.let { privateKeySeed ->
val pkSigning = OlmPkSigning()
try {
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
selfSigningPkSigning?.releaseSigning()
selfSigningPkSigning = pkSigning
selfSignedKeyIsTrusted = true
Timber.i("## CrossSigning - Loading master key success")
} else {
pkSigning.releaseSigning()
}
} catch (failure: Throwable) {
pkSigning.releaseSigning()
}
}
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
} else {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
val checkSelfTrust = checkSelfTrust()
if (checkSelfTrust.isVerified()) {
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey)
setUserKeysAsTrusted(userId, true)
}
return checkSelfTrust
}
}
/** /**
* *
* *
@ -374,7 +453,9 @@ internal class DefaultCrossSigningService @Inject constructor(
?.fromBase64NoPadding() ?.fromBase64NoPadding()
var isMaterKeyTrusted = false var isMaterKeyTrusted = false
if (masterPrivateKey != null) { if (myMasterKey.trustLevel?.locallyVerified == true) {
isMaterKeyTrusted = true
} else if (masterPrivateKey != null) {
// Check if private match public // Check if private match public
var olmPkSigning: OlmPkSigning? = null var olmPkSigning: OlmPkSigning? = null
try { try {
@ -507,7 +588,12 @@ internal class DefaultCrossSigningService @Inject constructor(
}.executeBy(taskExecutor) }.executeBy(taskExecutor)
} }
override fun signDevice(deviceId: String, callback: MatrixCallback<Unit>) { override fun markMyMasterKeyAsTrusted() {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
checkSelfTrust()
}
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) {
// This device should be yours // This device should be yours
val device = cryptoStore.getUserDevice(userId, deviceId) val device = cryptoStore.getUserDevice(userId, deviceId)
if (device == null) { if (device == null) {

View File

@ -18,16 +18,19 @@ package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createBackgroundHandler
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import java.util.concurrent.atomic.AtomicBoolean
import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject import javax.inject.Inject
@ -35,7 +38,7 @@ internal class ShieldTrustUpdater @Inject constructor(
private val eventBus: EventBus, private val eventBus: EventBus,
private val computeTrustTask: ComputeTrustTask, private val computeTrustTask: ComputeTrustTask,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
@CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration, private val coroutineDispatchers: MatrixCoroutineDispatchers,
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration, @SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
private val roomSummaryUpdater: RoomSummaryUpdater private val roomSummaryUpdater: RoomSummaryUpdater
) { ) {
@ -44,51 +47,41 @@ internal class ShieldTrustUpdater @Inject constructor(
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD") private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
} }
private val backgroundCryptoRealm = AtomicReference<Realm>()
private val backgroundSessionRealm = AtomicReference<Realm>() private val backgroundSessionRealm = AtomicReference<Realm>()
// private var cryptoDevicesResult: RealmResults<DeviceInfoEntity>? = null private val isStarted = AtomicBoolean()
// private val cryptoDeviceChangeListener = object : OrderedRealmCollectionChangeListener<RealmResults<DeviceInfoEntity>> {
// override fun onChange(t: RealmResults<DeviceInfoEntity>, changeSet: OrderedCollectionChangeSet) {
// val grouped = t.groupBy { it.userId }
// onCryptoDevicesChange(grouped.keys.mapNotNull { it })
// }
// }
fun start() { fun start() {
eventBus.register(this) if (isStarted.compareAndSet(false, true)) {
BACKGROUND_HANDLER.post { eventBus.register(this)
val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration) BACKGROUND_HANDLER.post {
backgroundCryptoRealm.set(cryptoRealm) backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
// cryptoDevicesResult = cryptoRealm.where<DeviceInfoEntity>().findAll() }
// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener)
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
} }
} }
fun stop() { fun stop() {
eventBus.unregister(this) if (isStarted.compareAndSet(true, false)) {
BACKGROUND_HANDLER.post { eventBus.unregister(this)
// cryptoDevicesResult?.removeAllChangeListeners() BACKGROUND_HANDLER.post {
backgroundCryptoRealm.getAndSet(null).also { backgroundSessionRealm.getAndSet(null).also {
it?.close() it?.close()
} }
backgroundSessionRealm.getAndSet(null).also {
it?.close()
} }
} }
} }
@Subscribe @Subscribe
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) { fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
taskExecutor.executorScope.launch { if (!isStarted.get()) {
return
}
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds)) val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
// We need to send that back to session base // We need to send that back to session base
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
backgroundSessionRealm.get().executeTransaction { realm -> backgroundSessionRealm.get()?.executeTransaction { realm ->
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust) roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
} }
} }
@ -97,34 +90,47 @@ internal class ShieldTrustUpdater @Inject constructor(
@Subscribe @Subscribe
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) { fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
if (!isStarted.get()) {
return
}
onCryptoDevicesChange(update.userIds) onCryptoDevicesChange(update.userIds)
} }
private fun onCryptoDevicesChange(users: List<String>) { private fun onCryptoDevicesChange(users: List<String>) {
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java) val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java)
.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) ?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
.findAll() ?.findAll()
.map { it.roomId } ?.map { it.roomId }
.distinct() ?.distinct()
val map = HashMap<String, List<String>>() val map = HashMap<String, List<String>>()
impactedRoomsId.forEach { roomId -> impactedRoomsId?.forEach { roomId ->
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId) backgroundSessionRealm.get()?.let { realm ->
.findAll() RoomMemberSummaryEntity.where(realm, roomId)
.let { results -> .findAll()
map[roomId] = results.map { it.userId } .let { results ->
} map[roomId] = results.map { it.userId }
}
}
} }
map.forEach { entry -> map.forEach { entry ->
val roomId = entry.key val roomId = entry.key
val userList = entry.value val userList = entry.value
taskExecutor.executorScope.launch { taskExecutor.executorScope.launch {
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList)) withContext(coroutineDispatchers.crypto) {
BACKGROUND_HANDLER.post { try {
backgroundSessionRealm.get().executeTransaction { realm -> // Can throw if the crypto database has been closed in between, in this case log and ignore?
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust) val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
BACKGROUND_HANDLER.post {
backgroundSessionRealm.get()?.executeTransaction { realm ->
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
}
}
} catch (failure: Throwable) {
Timber.e(failure)
} }
} }
} }

View File

@ -80,6 +80,7 @@ import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.android.internal.util.awaitCallback
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.olm.OlmException import org.matrix.olm.OlmException
@ -167,9 +168,7 @@ internal class DefaultKeysBackupService @Inject constructor(
runCatching { runCatching {
withContext(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
val olmPkDecryption = OlmPkDecryption() val olmPkDecryption = OlmPkDecryption()
val megolmBackupAuthData = MegolmBackupAuthData() val megolmBackupAuthData = if (password != null) {
if (password != null) {
// Generate a private key from the password // Generate a private key from the password
val backgroundProgressListener = if (progressListener == null) { val backgroundProgressListener = if (progressListener == null) {
null null
@ -188,25 +187,30 @@ internal class DefaultKeysBackupService @Inject constructor(
} }
val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey) MegolmBackupAuthData(
megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations privateKeySalt = generatePrivateKeyResult.salt,
privateKeyIterations = generatePrivateKeyResult.iterations
)
} else { } else {
val publicKey = olmPkDecryption.generateKey() val publicKey = olmPkDecryption.generateKey()
megolmBackupAuthData.publicKey = publicKey MegolmBackupAuthData(
publicKey = publicKey
)
} }
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) val signedMegolmBackupAuthData = megolmBackupAuthData.copy(
signatures = objectSigner.signObject(canonicalJson)
)
val megolmBackupCreationInfo = MegolmBackupCreationInfo() MegolmBackupCreationInfo(
megolmBackupCreationInfo.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
megolmBackupCreationInfo.authData = megolmBackupAuthData authData = signedMegolmBackupAuthData,
megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
)
megolmBackupCreationInfo
} }
}.foldToCallback(callback) }.foldToCallback(callback)
} }
@ -214,11 +218,12 @@ internal class DefaultKeysBackupService @Inject constructor(
override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
callback: MatrixCallback<KeysVersion>) { callback: MatrixCallback<KeysVersion>) {
val createKeysBackupVersionBody = CreateKeysBackupVersionBody()
createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java) val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? algorithm = keysBackupCreationInfo.algorithm,
authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
)
keysBackupStateManager.state = KeysBackupState.Enabling keysBackupStateManager.state = KeysBackupState.Enabling
@ -229,14 +234,14 @@ internal class DefaultKeysBackupService @Inject constructor(
// Reset backup markers. // Reset backup markers.
cryptoStore.resetBackupMarkers() cryptoStore.resetBackupMarkers()
val keyBackupVersion = KeysVersionResult() val keyBackupVersion = KeysVersionResult(
keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm algorithm = createKeysBackupVersionBody.algorithm,
keyBackupVersion.authData = createKeysBackupVersionBody.authData authData = createKeysBackupVersionBody.authData,
keyBackupVersion.version = data.version version = data.version,
// We can consider that the server does not have keys yet
// We can consider that the server does not have keys yet count = 0,
keyBackupVersion.count = 0 hash = null
keyBackupVersion.hash = null )
enableKeysBackup(keyBackupVersion) enableKeysBackup(keyBackupVersion)
@ -406,7 +411,7 @@ internal class DefaultKeysBackupService @Inject constructor(
return keysBackupVersionTrust return keysBackupVersionTrust
} }
val mySigs = authData.signatures?.get(userId) val mySigs = authData.signatures[userId]
if (mySigs.isNullOrEmpty()) { if (mySigs.isNullOrEmpty()) {
Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user")
return keysBackupVersionTrust return keysBackupVersionTrust
@ -469,8 +474,7 @@ internal class DefaultKeysBackupService @Inject constructor(
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
// Get current signatures, or create an empty set // Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() ?: HashMap()
?: HashMap()
if (trust) { if (trust) {
// Add current device signature // Add current device signature
@ -487,24 +491,23 @@ internal class DefaultKeysBackupService @Inject constructor(
} }
// Create an updated version of KeysVersionResult // Create an updated version of KeysVersionResult
val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!)
updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm
val newMegolmBackupAuthData = authData.copy() val newMegolmBackupAuthData = authData.copy()
val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap()
newSignatures[userId] = myUserSignatures newSignatures[userId] = myUserSignatures
newMegolmBackupAuthData.signatures = newSignatures val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy(
signatures = newSignatures
)
val moshi = MoshiProvider.providesMoshi() val moshi = MoshiProvider.providesMoshi()
val adapter = moshi.adapter(Map::class.java) val adapter = moshi.adapter(Map::class.java)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map<String, Any>? UpdateKeysBackupVersionBody(
algorithm = keysBackupVersion.algorithm,
updateKeysBackupVersionBody authData = adapter.fromJson(newMegolmBackupAuthDataWithNewSignature.toJsonString()) as Map<String, Any>?,
version = keysBackupVersion.version!!)
} }
// And send it to the homeserver // And send it to the homeserver
@ -513,13 +516,13 @@ internal class DefaultKeysBackupService @Inject constructor(
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
// Relaunch the state machine on this updated backup version // Relaunch the state machine on this updated backup version
val newKeysBackupVersion = KeysVersionResult() val newKeysBackupVersion = KeysVersionResult(
algorithm = keysBackupVersion.algorithm,
newKeysBackupVersion.version = keysBackupVersion.version authData = updateKeysBackupVersionBody.authData,
newKeysBackupVersion.algorithm = keysBackupVersion.algorithm version = keysBackupVersion.version,
newKeysBackupVersion.count = keysBackupVersion.count hash = keysBackupVersion.hash,
newKeysBackupVersion.hash = keysBackupVersion.hash count = keysBackupVersion.count
newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData )
checkAndStartWithKeysBackupVersion(newKeysBackupVersion) checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
@ -807,7 +810,10 @@ internal class DefaultKeysBackupService @Inject constructor(
// new key is sent // new key is sent
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
uiHandler.postDelayed({ backupKeys() }, delayInMs) cryptoCoroutineScope.launch {
delay(delayInMs)
uiHandler.post { backupKeys() }
}
} }
else -> { else -> {
Timber.v("maybeBackupKeys: Skip it because state: $state") Timber.v("maybeBackupKeys: Skip it because state: $state")
@ -1024,7 +1030,7 @@ internal class DefaultKeysBackupService @Inject constructor(
} }
// Extract the recovery key from the passphrase // Extract the recovery key from the passphrase
val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt!!, authData.privateKeyIterations!!, progressListener) val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt, authData.privateKeyIterations, progressListener)
return computeRecoveryKey(data) return computeRecoveryKey(data)
} }
@ -1178,14 +1184,16 @@ internal class DefaultKeysBackupService @Inject constructor(
// Gather data to send to the homeserver // Gather data to send to the homeserver
// roomId -> sessionId -> MXKeyBackupData // roomId -> sessionId -> MXKeyBackupData
val keysBackupData = KeysBackupData() val keysBackupData = KeysBackupData(
keysBackupData.roomIdToRoomKeysBackupData = HashMap() roomIdToRoomKeysBackupData = HashMap()
)
for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) {
val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper)
if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) {
val roomKeysBackupData = RoomKeysBackupData() val roomKeysBackupData = RoomKeysBackupData(
roomKeysBackupData.sessionIdToKeyBackupData = HashMap() sessionIdToKeyBackupData = HashMap()
)
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData
} }
@ -1301,24 +1309,21 @@ internal class DefaultKeysBackupService @Inject constructor(
} }
// Build backup data for that key // Build backup data for that key
val keyBackupData = KeyBackupData() return KeyBackupData(
try { firstMessageIndex = try {
keyBackupData.firstMessageIndex = olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex
} catch (e: OlmException) { } catch (e: OlmException) {
Timber.e(e, "OlmException") Timber.e(e, "OlmException")
} 0L
},
forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size,
isVerified = device?.isVerified == true,
keyBackupData.forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size sessionData = mapOf(
keyBackupData.isVerified = device?.isVerified == true "ciphertext" to encryptedSessionBackupData!!.mCipherText,
"mac" to encryptedSessionBackupData.mMac,
val data = mapOf( "ephemeral" to encryptedSessionBackupData.mEphemeralKey)
"ciphertext" to encryptedSessionBackupData!!.mCipherText, )
"mac" to encryptedSessionBackupData.mMac,
"ephemeral" to encryptedSessionBackupData.mEphemeralKey)
keyBackupData.sessionData = data
return keyBackupData
} }
@VisibleForTesting @VisibleForTesting
@ -1350,8 +1355,10 @@ internal class DefaultKeysBackupService @Inject constructor(
} }
if (sessionBackupData != null) { if (sessionBackupData != null) {
sessionBackupData.sessionId = sessionId sessionBackupData = sessionBackupData.copy(
sessionBackupData.roomId = roomId sessionId = sessionId,
roomId = roomId
)
} }
} }
@ -1370,11 +1377,12 @@ internal class DefaultKeysBackupService @Inject constructor(
@VisibleForTesting @VisibleForTesting
fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
callback: MatrixCallback<KeysVersion>) { callback: MatrixCallback<KeysVersion>) {
val createKeysBackupVersionBody = CreateKeysBackupVersionBody()
createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java) val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? algorithm = keysBackupCreationInfo.algorithm,
authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
)
createKeysBackupVersionTask createKeysBackupVersionTask
.configureWith(createKeysBackupVersionBody) { .configureWith(createKeysBackupVersionBody) {

View File

@ -30,26 +30,27 @@ data class MegolmBackupAuthData(
* The curve25519 public key used to encrypt the backups. * The curve25519 public key used to encrypt the backups.
*/ */
@Json(name = "public_key") @Json(name = "public_key")
var publicKey: String = "", val publicKey: String = "",
/** /**
* In case of a backup created from a password, the salt associated with the backup * In case of a backup created from a password, the salt associated with the backup
* private key. * private key.
*/ */
@Json(name = "private_key_salt") @Json(name = "private_key_salt")
var privateKeySalt: String? = null, val privateKeySalt: String? = null,
/** /**
* In case of a backup created from a password, the number of key derivations. * In case of a backup created from a password, the number of key derivations.
*/ */
@Json(name = "private_key_iterations") @Json(name = "private_key_iterations")
var privateKeyIterations: Int? = null, val privateKeyIterations: Int? = null,
/** /**
* Signatures of the public key. * Signatures of the public key.
* userId -> (deviceSignKeyId -> signature) * userId -> (deviceSignKeyId -> signature)
*/ */
var signatures: Map<String, Map<String, String>>? = null @Json(name = "signatures")
val signatures: Map<String, Map<String, String>>? = null
) { ) {
fun toJsonString(): String { fun toJsonString(): String {

View File

@ -19,20 +19,19 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model
/** /**
* Data retrieved from Olm library. algorithm and authData will be send to the homeserver, and recoveryKey will be displayed to the user * Data retrieved from Olm library. algorithm and authData will be send to the homeserver, and recoveryKey will be displayed to the user
*/ */
class MegolmBackupCreationInfo { data class MegolmBackupCreationInfo(
/**
* The algorithm used for storing backups [org.matrix.androidsdk.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP].
*/
val algorithm: String = "",
/** /**
* The algorithm used for storing backups [org.matrix.androidsdk.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP]. * Authentication data.
*/ */
var algorithm: String = "" val authData: MegolmBackupAuthData? = null,
/** /**
* Authentication data. * The Base58 recovery key.
*/ */
var authData: MegolmBackupAuthData? = null val recoveryKey: String = ""
)
/**
* The Base58 recovery key.
*/
var recoveryKey: String = ""
}

View File

@ -16,7 +16,21 @@
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class CreateKeysBackupVersionBody : KeysAlgorithmAndData() data class CreateKeysBackupVersionBody(
/**
* The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
*/
@Json(name = "algorithm")
override val algorithm: String? = null,
/**
* algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
*/
@Json(name = "auth_data")
override val authData: JsonDict? = null
) : KeysAlgorithmAndData

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.parsing.ForceToBoolean
/** /**
* Backup data for one key. * Backup data for one key.
@ -29,25 +30,27 @@ data class KeyBackupData(
* Required. The index of the first message in the session that the key can decrypt. * Required. The index of the first message in the session that the key can decrypt.
*/ */
@Json(name = "first_message_index") @Json(name = "first_message_index")
var firstMessageIndex: Long = 0, val firstMessageIndex: Long = 0,
/** /**
* Required. The number of times this key has been forwarded. * Required. The number of times this key has been forwarded.
*/ */
@Json(name = "forwarded_count") @Json(name = "forwarded_count")
var forwardedCount: Int = 0, val forwardedCount: Int = 0,
/** /**
* Whether the device backing up the key has verified the device that the key is from. * Whether the device backing up the key has verified the device that the key is from.
* Force to boolean because of https://github.com/matrix-org/synapse/issues/6977
*/ */
@ForceToBoolean
@Json(name = "is_verified") @Json(name = "is_verified")
var isVerified: Boolean = false, val isVerified: Boolean = false,
/** /**
* Algorithm-dependent data. * Algorithm-dependent data.
*/ */
@Json(name = "session_data") @Json(name = "session_data")
var sessionData: Map<String, Any>? = null val sessionData: Map<String, Any>? = null
) { ) {
fun toJsonString(): String { fun toJsonString(): String {

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
import com.squareup.moshi.Json
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
@ -38,19 +37,17 @@ import im.vector.matrix.android.internal.di.MoshiProvider
* } * }
* </pre> * </pre>
*/ */
open class KeysAlgorithmAndData { interface KeysAlgorithmAndData {
/** /**
* The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
*/ */
@Json(name = "algorithm") val algorithm: String?
var algorithm: String? = null
/** /**
* algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData] * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
*/ */
@Json(name = "auth_data") val authData: JsonDict?
var authData: JsonDict? = null
/** /**
* Facility method to convert authData to a MegolmBackupAuthData object * Facility method to convert authData to a MegolmBackupAuthData object

View File

@ -24,9 +24,7 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class KeysBackupData( data class KeysBackupData(
// the keys are the room IDs, and the values are RoomKeysBackupData // the keys are the room IDs, and the values are RoomKeysBackupData
@Json(name = "rooms") @Json(name = "rooms")
var roomIdToRoomKeysBackupData: MutableMap<String, RoomKeysBackupData> = HashMap() val roomIdToRoomKeysBackupData: MutableMap<String, RoomKeysBackupData> = HashMap()
) )

View File

@ -16,16 +16,33 @@
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class KeysVersionResult( data class KeysVersionResult(
/**
* The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
*/
@Json(name = "algorithm")
override val algorithm: String? = null,
/**
* algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
*/
@Json(name = "auth_data")
override val authData: JsonDict? = null,
// the backup version // the backup version
var version: String? = null, @Json(name = "version")
val version: String? = null,
// The hash value which is an opaque string representing stored keys in the backup // The hash value which is an opaque string representing stored keys in the backup
var hash: String? = null, @Json(name = "hash")
val hash: String? = null,
// The number of keys stored in the backup. // The number of keys stored in the backup.
var count: Int? = null @Json(name = "count")
) : KeysAlgorithmAndData() val count: Int? = null
) : KeysAlgorithmAndData

View File

@ -24,8 +24,7 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomKeysBackupData( data class RoomKeysBackupData(
// the keys are the session IDs, and the values are KeyBackupData // the keys are the session IDs, and the values are KeyBackupData
@Json(name = "sessions") @Json(name = "sessions")
var sessionIdToKeyBackupData: MutableMap<String, KeyBackupData> = HashMap() val sessionIdToKeyBackupData: MutableMap<String, KeyBackupData> = HashMap()
) )

View File

@ -16,10 +16,25 @@
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class UpdateKeysBackupVersionBody( data class UpdateKeysBackupVersionBody(
/**
* The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
*/
@Json(name = "algorithm")
override val algorithm: String? = null,
/**
* algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
*/
@Json(name = "auth_data")
override val authData: JsonDict? = null,
// the backup version, mandatory // the backup version, mandatory
@Json(name = "version")
val version: String val version: String
) : KeysAlgorithmAndData() ) : KeysAlgorithmAndData

View File

@ -28,10 +28,6 @@ data class CryptoDeviceInfo(
override val keys: Map<String, String>? = null, override val keys: Map<String, String>? = null,
override val signatures: Map<String, Map<String, String>>? = null, override val signatures: Map<String, Map<String, String>>? = null,
val unsigned: JsonDict? = null, val unsigned: JsonDict? = null,
// TODO how to store if this device is verified by a user SSK, or is legacy trusted?
// I need to know if it is trusted via cross signing (Trusted because bob verified it)
var trustLevel: DeviceTrustLevel? = null, var trustLevel: DeviceTrustLevel? = null,
var isBlocked: Boolean = false var isBlocked: Boolean = false
) : CryptoInfo { ) : CryptoInfo {
@ -75,19 +71,6 @@ data class CryptoDeviceInfo(
keys?.let { map["keys"] = it } keys?.let { map["keys"] = it }
return map return map
} }
//
// /**
// * @return a dictionary of the parameters
// */
// fun toDeviceKeys(): DeviceKeys {
// return DeviceKeys(
// userId = userId,
// deviceId = deviceId,
// algorithms = algorithms!!,
// keys = keys!!,
// signatures = signatures!!
// )
// }
} }
internal fun CryptoDeviceInfo.toRest(): RestDeviceInfo { internal fun CryptoDeviceInfo.toRest(): RestDeviceInfo {

View File

@ -26,48 +26,47 @@ import java.io.Serializable
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MXDeviceInfo( data class MXDeviceInfo(
/** /**
* The id of this device. * The id of this device.
*/ */
@Json(name = "device_id") @Json(name = "device_id")
var deviceId: String, val deviceId: String,
/** /**
* the user id * the user id
*/ */
@Json(name = "user_id") @Json(name = "user_id")
var userId: String, val userId: String,
/** /**
* The list of algorithms supported by this device. * The list of algorithms supported by this device.
*/ */
@Json(name = "algorithms") @Json(name = "algorithms")
var algorithms: List<String>? = null, val algorithms: List<String>? = null,
/** /**
* A map from "<key type>:<deviceId>" to "<base64-encoded key>". * A map from "<key type>:<deviceId>" to "<base64-encoded key>".
*/ */
@Json(name = "keys") @Json(name = "keys")
var keys: Map<String, String>? = null, val keys: Map<String, String>? = null,
/** /**
* The signature of this MXDeviceInfo. * The signature of this MXDeviceInfo.
* A map from "<userId>" to a map from "<key type>:<deviceId>" to "<signature>" * A map from "<userId>" to a map from "<key type>:<deviceId>" to "<signature>"
*/ */
@Json(name = "signatures") @Json(name = "signatures")
var signatures: Map<String, Map<String, String>>? = null, val signatures: Map<String, Map<String, String>>? = null,
/* /*
* Additional data from the home server. * Additional data from the home server.
*/ */
@Json(name = "unsigned") @Json(name = "unsigned")
var unsigned: JsonDict? = null, val unsigned: JsonDict? = null,
/** /**
* Verification state of this device. * Verification state of this device.
*/ */
var verified: Int = DEVICE_VERIFICATION_UNKNOWN val verified: Int = DEVICE_VERIFICATION_UNKNOWN
) : Serializable { ) : Serializable {
/** /**
* Tells if the device is unknown * Tells if the device is unknown
@ -137,11 +136,11 @@ data class MXDeviceInfo(
map["user_id"] = userId map["user_id"] = userId
if (null != algorithms) { if (null != algorithms) {
map["algorithms"] = algorithms!! map["algorithms"] = algorithms
} }
if (null != keys) { if (null != keys) {
map["keys"] = keys!! map["keys"] = keys
} }
return map return map

View File

@ -116,16 +116,16 @@ class OlmInboundGroupSessionWrapper : Serializable {
return null return null
} }
MegolmSessionData().also { MegolmSessionData(
it.senderClaimedEd25519Key = keysClaimed?.get("ed25519") senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
it.forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!) forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
it.senderKey = senderKey senderKey = senderKey,
it.senderClaimedKeys = keysClaimed senderClaimedKeys = keysClaimed,
it.roomId = roomId roomId = roomId,
it.sessionId = olmInboundGroupSession!!.sessionIdentifier() sessionId = olmInboundGroupSession!!.sessionIdentifier(),
it.sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex) sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex),
it.algorithm = MXCRYPTO_ALGORITHM_MEGOLM algorithm = MXCRYPTO_ALGORITHM_MEGOLM
} )
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed") Timber.e(e, "## export() : senderKey $senderKey failed")
null null

View File

@ -23,22 +23,21 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class EncryptionEventContent( data class EncryptionEventContent(
/** /**
* Required. The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'. * Required. The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'.
*/ */
@Json(name = "algorithm") @Json(name = "algorithm")
var algorithm: String, val algorithm: String,
/** /**
* How long the session should be used before changing it. 604800000 (a week) is the recommended default. * How long the session should be used before changing it. 604800000 (a week) is the recommended default.
*/ */
@Json(name = "rotation_period_ms") @Json(name = "rotation_period_ms")
var rotationPeriodMs: Long? = null, val rotationPeriodMs: Long? = null,
/** /**
* How many messages should be sent before changing the session. 100 is the recommended default. * How many messages should be sent before changing the session. 100 is the recommended default.
*/ */
@Json(name = "rotation_period_msgs") @Json(name = "rotation_period_msgs")
var rotationPeriodMsgs: Long? = null val rotationPeriodMsgs: Long? = null
) )

View File

@ -20,12 +20,11 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class NewDeviceContent( data class NewDeviceContent(
// the device id // the device id
@Json(name = "device_id") @Json(name = "device_id")
var deviceId: String? = null, val deviceId: String? = null,
// the room ids list // the room ids list
@Json(name = "rooms") @Json(name = "rooms")
var rooms: List<String>? = null val rooms: List<String>? = null
) )

View File

@ -27,11 +27,11 @@ data class OlmEventContent(
* *
*/ */
@Json(name = "ciphertext") @Json(name = "ciphertext")
var ciphertext: Map<String, Any>? = null, val ciphertext: Map<String, Any>? = null,
/** /**
* the sender key * the sender key
*/ */
@Json(name = "sender_key") @Json(name = "sender_key")
var senderKey: String? = null val senderKey: String? = null
) )

View File

@ -30,31 +30,31 @@ data class DeviceInfo(
* The owner user id (not documented and useless but the homeserver sent it. You should not need it) * The owner user id (not documented and useless but the homeserver sent it. You should not need it)
*/ */
@Json(name = "user_id") @Json(name = "user_id")
var user_id: String? = null, val user_id: String? = null,
/** /**
* The device id * The device id
*/ */
@Json(name = "device_id") @Json(name = "device_id")
var deviceId: String? = null, val deviceId: String? = null,
/** /**
* The device display name * The device display name
*/ */
@Json(name = "display_name") @Json(name = "display_name")
var displayName: String? = null, val displayName: String? = null,
/** /**
* The last time this device has been seen. * The last time this device has been seen.
*/ */
@Json(name = "last_seen_ts") @Json(name = "last_seen_ts")
var lastSeenTs: Long? = null, val lastSeenTs: Long? = null,
/** /**
* The last ip address * The last ip address
*/ */
@Json(name = "last_seen_ip") @Json(name = "last_seen_ip")
var lastSeenIp: String? = null val lastSeenIp: String? = null
) : DatedObject { ) : DatedObject {
override val date: Long override val date: Long

View File

@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class DevicesListResponse( data class DevicesListResponse(
@Json(name = "devices") @Json(name = "devices")
var devices: List<DeviceInfo>? = null val devices: List<DeviceInfo>? = null
) )

View File

@ -27,38 +27,38 @@ data class EncryptedFileInfo(
* Required. The URL to the file. * Required. The URL to the file.
*/ */
@Json(name = "url") @Json(name = "url")
var url: String? = null, val url: String? = null,
/** /**
* Not documented * Not documented
*/ */
@Json(name = "mimetype") @Json(name = "mimetype")
var mimetype: String? = null, val mimetype: String? = null,
/** /**
* Required. A JSON Web Key object. * Required. A JSON Web Key object.
*/ */
@Json(name = "key") @Json(name = "key")
var key: EncryptedFileKey? = null, val key: EncryptedFileKey? = null,
/** /**
* Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64. * Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64.
*/ */
@Json(name = "iv") @Json(name = "iv")
var iv: String? = null, val iv: String? = null,
/** /**
* Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. * Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64.
* Clients should support the SHA-256 hash, which uses the key "sha256". * Clients should support the SHA-256 hash, which uses the key "sha256".
*/ */
@Json(name = "hashes") @Json(name = "hashes")
var hashes: Map<String, String>? = null, val hashes: Map<String, String>? = null,
/** /**
* Required. Version of the encrypted attachments protocol. Must be "v2". * Required. Version of the encrypted attachments protocol. Must be "v2".
*/ */
@Json(name = "v") @Json(name = "v")
var v: String? = null val v: String? = null
) { ) {
/** /**
* Check what the spec tells us * Check what the spec tells us

View File

@ -24,31 +24,31 @@ data class EncryptedFileKey(
* Required. Algorithm. Must be "A256CTR". * Required. Algorithm. Must be "A256CTR".
*/ */
@Json(name = "alg") @Json(name = "alg")
var alg: String? = null, val alg: String? = null,
/** /**
* Required. Extractable. Must be true. This is a W3C extension. * Required. Extractable. Must be true. This is a W3C extension.
*/ */
@Json(name = "ext") @Json(name = "ext")
var ext: Boolean? = null, val ext: Boolean? = null,
/** /**
* Required. Key operations. Must at least contain "encrypt" and "decrypt". * Required. Key operations. Must at least contain "encrypt" and "decrypt".
*/ */
@Json(name = "key_ops") @Json(name = "key_ops")
var key_ops: List<String>? = null, val key_ops: List<String>? = null,
/** /**
* Required. Key type. Must be "oct". * Required. Key type. Must be "oct".
*/ */
@Json(name = "kty") @Json(name = "kty")
var kty: String? = null, val kty: String? = null,
/** /**
* Required. The key, encoded as urlsafe unpadded base64. * Required. The key, encoded as urlsafe unpadded base64.
*/ */
@Json(name = "k") @Json(name = "k")
var k: String? = null val k: String? = null
) { ) {
/** /**
* Check what the spec tells us * Check what the spec tells us
@ -62,7 +62,7 @@ data class EncryptedFileKey(
return false return false
} }
if (key_ops?.contains("encrypt") != true || key_ops?.contains("decrypt") != true) { if (key_ops?.contains("encrypt") != true || !key_ops.contains("decrypt")) {
return false return false
} }

View File

@ -21,11 +21,12 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class EncryptedMessage( data class EncryptedMessage(
var algorithm: String? = null, @Json(name = "algorithm")
val algorithm: String? = null,
@Json(name = "sender_key") @Json(name = "sender_key")
var senderKey: String? = null, val senderKey: String? = null,
@Json(name = "ciphertext") @Json(name = "ciphertext")
var cipherText: Map<String, Any>? = null val cipherText: Map<String, Any>? = null
) : SendToDeviceObject ) : SendToDeviceObject

View File

@ -25,9 +25,9 @@ import com.squareup.moshi.JsonClass
internal data class KeyChangesResponse( internal data class KeyChangesResponse(
// list of user ids which have new devices // list of user ids which have new devices
@Json(name = "changed") @Json(name = "changed")
var changed: List<String>? = null, val changed: List<String>? = null,
// List of user ids who are no more tracked. // List of user ids who are no more tracked.
@Json(name = "left") @Json(name = "left")
var left: List<String>? = null val left: List<String>? = null
) )

View File

@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDon
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class KeyVerificationDone( internal data class KeyVerificationDone(
@Json(name = "transaction_id") override var transactionID: String? = null @Json(name = "transaction_id") override val transactionID: String? = null
) : SendToDeviceObject, VerificationInfoDone { ) : SendToDeviceObject, VerificationInfoDone {
override fun toSendToDeviceObject() = this override fun toSendToDeviceObject() = this

View File

@ -27,7 +27,7 @@ internal data class KeyVerificationRequest(
@Json(name = "from_device") override val fromDevice: String?, @Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>, @Json(name = "methods") override val methods: List<String>,
@Json(name = "timestamp") override val timestamp: Long?, @Json(name = "timestamp") override val timestamp: Long?,
@Json(name = "transaction_id") override var transactionID: String? = null @Json(name = "transaction_id") override val transactionID: String? = null
) : SendToDeviceObject, VerificationInfoRequest { ) : SendToDeviceObject, VerificationInfoRequest {

View File

@ -24,16 +24,15 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class KeysClaimBody( internal data class KeysClaimBody(
/** /**
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default. * The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
*/ */
@Json(name = "timeout") @Json(name = "timeout")
var timeout: Int? = null, val timeout: Int? = null,
/** /**
* Required. The keys to be claimed. A map from user ID, to a map from device ID to algorithm name. * Required. The keys to be claimed. A map from user ID, to a map from device ID to algorithm name.
*/ */
@Json(name = "one_time_keys") @Json(name = "one_time_keys")
var oneTimeKeys: Map<String, Map<String, String>> val oneTimeKeys: Map<String, Map<String, String>>
) )

View File

@ -24,11 +24,10 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class KeysClaimResponse( internal data class KeysClaimResponse(
/** /**
* The requested keys ordered by device by user. * The requested keys ordered by device by user.
* TODO Type does not match spec, should be Map<String, JsonDict> * TODO Type does not match spec, should be Map<String, JsonDict>
*/ */
@Json(name = "one_time_keys") @Json(name = "one_time_keys")
var oneTimeKeys: Map<String, Map<String, Map<String, Map<String, Any>>>>? = null val oneTimeKeys: Map<String, Map<String, Map<String, Map<String, Any>>>>? = null
) )

View File

@ -25,12 +25,11 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class KeysQueryBody( internal data class KeysQueryBody(
/** /**
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default. * The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
*/ */
@Json(name = "timeout") @Json(name = "timeout")
var timeout: Int? = null, val timeout: Int? = null,
/** /**
* Required. The keys to be downloaded. * Required. The keys to be downloaded.
@ -45,6 +44,5 @@ internal data class KeysQueryBody(
* by the notification in that sync. * by the notification in that sync.
*/ */
@Json(name = "token") @Json(name = "token")
var token: String? = null val token: String? = null
) )

View File

@ -23,13 +23,11 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class KeysUploadResponse( internal data class KeysUploadResponse(
/** /**
* The count per algorithm as returned by the home server: a map (algorithm to count). * The count per algorithm as returned by the home server: a map (algorithm to count).
*/ */
@Json(name = "one_time_key_counts") @Json(name = "one_time_key_counts")
var oneTimeKeyCounts: Map<String, Int>? = null val oneTimeKeyCounts: Map<String, Int>? = null
) { ) {
/** /**
* Helper methods to extract information from 'oneTimeKeyCounts' * Helper methods to extract information from 'oneTimeKeyCounts'

View File

@ -25,14 +25,14 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomKeyRequestBody( data class RoomKeyRequestBody(
@Json(name = "algorithm") @Json(name = "algorithm")
var algorithm: String? = null, val algorithm: String? = null,
@Json(name = "room_id") @Json(name = "room_id")
var roomId: String? = null, val roomId: String? = null,
@Json(name = "sender_key") @Json(name = "sender_key")
var senderKey: String? = null, val senderKey: String? = null,
@Json(name = "session_id") @Json(name = "session_id")
var sessionId: String? = null val sessionId: String? = null
) )

View File

@ -15,21 +15,17 @@
*/ */
package im.vector.matrix.android.internal.crypto.model.rest package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
/** /**
* Parent class representing an room key action request * Interface representing an room key action request
* Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare]
*/ */
internal open class RoomKeyShare : SendToDeviceObject { internal interface RoomKeyShare : SendToDeviceObject {
var action: String? = null val action: String?
@Json(name = "requesting_device_id") val requestingDeviceId: String?
var requestingDeviceId: String? = null
@Json(name = "request_id") val requestId: String?
var requestId: String? = null
companion object { companion object {
const val ACTION_SHARE_REQUEST = "request" const val ACTION_SHARE_REQUEST = "request"

View File

@ -15,14 +15,21 @@
*/ */
package im.vector.matrix.android.internal.crypto.model.rest package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare.Companion.ACTION_SHARE_CANCELLATION
/** /**
* Class representing an room key request cancellation content * Class representing a room key request cancellation content
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal class RoomKeyShareCancellation : RoomKeyShare() { internal data class RoomKeyShareCancellation(
init { @Json(name = "action")
action = ACTION_SHARE_CANCELLATION override val action: String? = ACTION_SHARE_CANCELLATION,
}
} @Json(name = "requesting_device_id")
override val requestingDeviceId: String? = null,
@Json(name = "request_id")
override val requestId: String? = null
) : RoomKeyShare

View File

@ -16,16 +16,23 @@
*/ */
package im.vector.matrix.android.internal.crypto.model.rest package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
/** /**
* Class representing an room key request content * Class representing a room key request content
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal class RoomKeyShareRequest : RoomKeyShare() { internal data class RoomKeyShareRequest(
var body: RoomKeyRequestBody? = null @Json(name = "action")
override val action: String? = RoomKeyShare.ACTION_SHARE_REQUEST,
init { @Json(name = "requesting_device_id")
action = ACTION_SHARE_REQUEST override val requestingDeviceId: String? = null,
}
} @Json(name = "request_id")
override val requestId: String? = null,
@Json(name = "body")
val body: RoomKeyRequestBody? = null
) : RoomKeyShare

View File

@ -25,5 +25,5 @@ internal data class UpdateDeviceInfoBody(
* The new display name for this device. If not given, the display name is unchanged. * The new display name for this device. If not given, the display name is unchanged.
*/ */
@Json(name = "display_name") @Json(name = "display_name")
var displayName: String? = null val displayName: String? = null
) )

View File

@ -17,37 +17,42 @@
package im.vector.matrix.android.internal.crypto.secrets package im.vector.matrix.android.internal.crypto.secrets
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
import im.vector.matrix.android.api.session.securestorage.KeyInfo import im.vector.matrix.android.api.session.securestorage.KeyInfo
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
import im.vector.matrix.android.api.session.securestorage.KeySigner import im.vector.matrix.android.api.session.securestorage.KeySigner
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
import im.vector.matrix.android.internal.crypto.tools.HkdfSha256
import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption
import im.vector.matrix.android.internal.crypto.tools.withOlmEncryption
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.olm.OlmPkMessage import org.matrix.olm.OlmPkMessage
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject import javax.inject.Inject
import kotlin.experimental.and
private data class Key(
val publicKey: String,
@Suppress("ArrayInDataClass")
val privateKey: ByteArray
)
internal class DefaultSharedSecretStorageService @Inject constructor( internal class DefaultSharedSecretStorageService @Inject constructor(
private val accountDataService: AccountDataService, private val accountDataService: AccountDataService,
@ -57,14 +62,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
override fun generateKey(keyId: String, override fun generateKey(keyId: String,
keyName: String, keyName: String,
keySigner: KeySigner, keySigner: KeySigner?,
callback: MatrixCallback<SsssKeyCreationInfo>) { callback: MatrixCallback<SsssKeyCreationInfo>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val key = try { val key = try {
withOlmDecryption { olmPkDecryption -> ByteArray(32).also {
val pubKey = olmPkDecryption.generateKey() SecureRandom().nextBytes(it)
val privateKey = olmPkDecryption.privateKey()
Key(pubKey, privateKey)
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
callback.onFailure(failure) callback.onFailure(failure)
@ -73,12 +76,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
val storageKeyContent = SecretStorageKeyContent( val storageKeyContent = SecretStorageKeyContent(
name = keyName, name = keyName,
algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2, algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2,
passphrase = null, passphrase = null
publicKey = key.publicKey
) )
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let { val signedContent = keySigner?.sign(storageKeyContent.canonicalSignable())?.let {
storageKeyContent.copy( storageKeyContent.copy(
signatures = it signatures = it
) )
@ -96,7 +98,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
callback.onSuccess(SsssKeyCreationInfo( callback.onSuccess(SsssKeyCreationInfo(
keyId = keyId, keyId = keyId,
content = storageKeyContent, content = storageKeyContent,
recoveryKey = computeRecoveryKey(key.privateKey) recoveryKey = computeRecoveryKey(key)
)) ))
} }
} }
@ -113,19 +115,9 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
val pubKey = try {
withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(privatePart.privateKey)
}
} catch (failure: Throwable) {
callback.onFailure(failure)
return@launch
}
val storageKeyContent = SecretStorageKeyContent( val storageKeyContent = SecretStorageKeyContent(
algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2, algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2,
passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt), passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt)
publicKey = pubKey
) )
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let { val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
@ -188,24 +180,19 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
return getKey(keyId) return getKey(keyId)
} }
override fun storeSecret(name: String, secretBase64: String, keys: List<String>?, callback: MatrixCallback<Unit>) { override fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>, callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val encryptedContents = HashMap<String, EncryptedSecretContent>() val encryptedContents = HashMap<String, EncryptedSecretContent>()
try { try {
if (keys.isNullOrEmpty()) { keys.forEach {
// use default key val keyId = it.keyId
when (val key = getDefaultKey()) { // encrypt the content
when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) {
is KeyInfoResult.Success -> { is KeyInfoResult.Success -> {
if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) { if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) {
val encryptedResult = withOlmEncryption { olmEncrypt -> encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let {
olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey) encryptedContents[key.keyInfo.id] = it
olmEncrypt.encrypt(secretBase64)
} }
encryptedContents[key.keyInfo.id] = EncryptedSecretContent(
ciphertext = encryptedResult.mCipherText,
ephemeral = encryptedResult.mEphemeralKey,
mac = encryptedResult.mMac
)
} else { } else {
// Unknown algorithm // Unknown algorithm
callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
@ -217,34 +204,6 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
return@launch return@launch
} }
} }
} else {
keys.forEach {
val keyId = it
// encrypt the content
when (val key = getKey(keyId)) {
is KeyInfoResult.Success -> {
if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
val encryptedResult = withOlmEncryption { olmEncrypt ->
olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
olmEncrypt.encrypt(secretBase64)
}
encryptedContents[keyId] = EncryptedSecretContent(
ciphertext = encryptedResult.mCipherText,
ephemeral = encryptedResult.mEphemeralKey,
mac = encryptedResult.mMac
)
} else {
// Unknown algorithm
callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
return@launch
}
}
is KeyInfoResult.Error -> {
callback.onFailure(key.error)
return@launch
}
}
}
} }
accountDataService.updateAccountData( accountDataService.updateAccountData(
@ -258,8 +217,109 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
callback.onFailure(failure) callback.onFailure(failure)
} }
} }
}
// Add default key /**
* Encryption algorithm m.secret_storage.v1.aes-hmac-sha2
* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. The data is encrypted and MACed as follows:
*
* Given the secret storage key, generate 64 bytes by performing an HKDF with SHA-256 as the hash, a salt of 32 bytes
* of 0, and with the secret name as the info.
*
* The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
*
* Generate 16 random bytes, set bit 63 to 0 (in order to work around differences in AES-CTR implementations), and use
* this as the AES initialization vector.
* This becomes the iv property, encoded using base64.
*
* Encrypt the data using AES-CTR-256 using the AES key generated above.
*
* This encrypted data, encoded using base64, becomes the ciphertext property.
*
* Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256 using the MAC key generated above.
* The resulting MAC is base64-encoded and becomes the mac property.
* (We use AES-CTR to match file encryption and key exports.)
*/
@Throws
private fun encryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, clearDataBase64: String): EncryptedSecretContent {
secretKey as RawBytesKeySpec
val pseudoRandomKey = HkdfSha256.deriveSecret(
secretKey.privateKey,
ByteArray(32) { 0.toByte() },
secretName.toByteArray(),
64)
// The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
val macKey = pseudoRandomKey.copyOfRange(32, 64)
val secureRandom = SecureRandom()
val iv = ByteArray(16)
secureRandom.nextBytes(iv)
// clear bit 63 of the salt to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of salt is a price we have to pay.
iv[9] = iv[9] and 0x7f
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
val secretKeySpec = SecretKeySpec(aesKey, "AES")
val ivParameterSpec = IvParameterSpec(iv)
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
// secret are not that big, just do Final
val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64NoPadding())
require(cipherBytes.isNotEmpty())
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
val mac = Mac.getInstance("HmacSHA256")
mac.init(macKeySpec)
val digest = mac.doFinal(cipherBytes)
return EncryptedSecretContent(
ciphertext = cipherBytes.toBase64NoPadding(),
initializationVector = iv.toBase64NoPadding(),
mac = digest.toBase64NoPadding()
)
}
private fun decryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, cipherContent: EncryptedSecretContent): String {
secretKey as RawBytesKeySpec
val pseudoRandomKey = HkdfSha256.deriveSecret(
secretKey.privateKey,
ByteArray(32) { 0.toByte() },
secretName.toByteArray(),
64)
// The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
val macKey = pseudoRandomKey.copyOfRange(32, 64)
val iv = cipherContent.initializationVector?.fromBase64NoPadding() ?: ByteArray(16)
val cipherRawBytes = cipherContent.ciphertext!!.fromBase64NoPadding()
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
val secretKeySpec = SecretKeySpec(aesKey, "AES")
val ivParameterSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
// secret are not that big, just do Final
val decryptedSecret = cipher.doFinal(cipherRawBytes)
require(decryptedSecret.isNotEmpty())
// Check Signature
val macKeySpec = SecretKeySpec(macKey, "HmacSHA256")
val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) }
val digest = mac.doFinal(cipherRawBytes)
if (!cipherContent.mac?.fromBase64NoPadding()?.contentEquals(digest).orFalse()) {
throw SharedSecretStorageError.BadMac
} else {
// we are good
return decryptedSecret.toBase64NoPadding()
}
} }
override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> { override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
@ -299,7 +359,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
val algorithm = key.keyInfo.content val algorithm = key.keyInfo.content
if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
val keySpec = secretKey as? Curve25519AesSha2KeySpec ?: return Unit.also { val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also {
callback.onFailure(SharedSecretStorageError.BadKeyFormat) callback.onFailure(SharedSecretStorageError.BadKeyFormat)
} }
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
@ -317,6 +377,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
} }
}.foldToCallback(callback) }.foldToCallback(callback)
} }
} else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also {
callback.onFailure(SharedSecretStorageError.BadKeyFormat)
}
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
kotlin.runCatching {
decryptAesHmacSha2(keySpec, name, secretContent)
}.foldToCallback(callback)
}
} else { } else {
callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")) callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: ""))
} }
@ -327,4 +396,37 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
const val ENCRYPTED = "encrypted" const val ENCRYPTED = "encrypted"
const val DEFAULT_KEY_ID = "m.secret_storage.default_key" const val DEFAULT_KEY_ID = "m.secret_storage.default_key"
} }
override fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?): IntegrityResult {
if (secretNames.isEmpty()) {
return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret("none"))
}
val keyInfoResult = if (keyId == null) {
getDefaultKey()
} else {
getKey(keyId)
}
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2
|| keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
// Unsupported algorithm
return IntegrityResult.Error(
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")
)
}
secretNames.forEach { secretName ->
val secretEvent = accountDataService.getAccountDataEvent(secretName)
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret(secretName))
if ((secretEvent.content["encrypted"] as? Map<*, *>)?.get(keyInfo.id) == null) {
return IntegrityResult.Error(SharedSecretStorageError.SecretNotEncryptedWithKey(secretName, keyInfo.id))
}
}
return IntegrityResult.Success(keyInfo.content.passphrase != null)
}
} }

View File

@ -20,6 +20,7 @@ package im.vector.matrix.android.internal.crypto.store
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
@ -382,7 +383,7 @@ internal interface IMXCryptoStore {
* *
* @param incomingRoomKeyRequest the incoming key request * @param incomingRoomKeyRequest the incoming key request
*/ */
fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest) fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon)
/** /**
* Search an IncomingRoomKeyRequest * Search an IncomingRoomKeyRequest
@ -412,6 +413,8 @@ internal interface IMXCryptoStore {
fun getLiveCrossSigningInfo(userId: String) : LiveData<Optional<MXCrossSigningInfo>> fun getLiveCrossSigningInfo(userId: String) : LiveData<Optional<MXCrossSigningInfo>>
fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?)
fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean)
fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?)
fun getCrossSigningPrivateKeys() : PrivateKeysInfo? fun getCrossSigningPrivateKeys() : PrivateKeysInfo?

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
@ -888,7 +889,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest) { override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon) {
doRealmTransaction(realmConfiguration) { doRealmTransaction(realmConfiguration) {
it.where<IncomingRoomKeyRequestEntity>() it.where<IncomingRoomKeyRequestEntity>()
.equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId)
@ -1093,6 +1094,23 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) {
doRealmTransaction(realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { myUserId ->
CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity ->
val level = xInfoEntity.trustLevelEntity
if (level == null) {
val newLevel = realm.createObject(TrustLevelEntity::class.java)
newLevel.locallyVerified = trusted
xInfoEntity.trustLevelEntity = newLevel
} else {
level.locallyVerified = trusted
}
}
}
}
}
private fun addOrUpdateCrossSigningInfo(realm: Realm, userId: String, info: MXCrossSigningInfo?): CrossSigningInfoEntity? { private fun addOrUpdateCrossSigningInfo(realm: Realm, userId: String, info: MXCrossSigningInfo?): CrossSigningInfoEntity? {
var existing = CrossSigningInfoEntity.get(realm, userId) var existing = CrossSigningInfoEntity.get(realm, userId)
if (info == null) { if (info == null) {

View File

@ -32,17 +32,17 @@ internal open class IncomingRoomKeyRequestEntity(
) : RealmObject() { ) : RealmObject() {
fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest { fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest {
return IncomingRoomKeyRequest().also { return IncomingRoomKeyRequest(
it.requestId = requestId requestId = requestId,
it.userId = userId userId = userId,
it.deviceId = deviceId deviceId = deviceId,
it.requestBody = RoomKeyRequestBody().apply { requestBody = RoomKeyRequestBody(
algorithm = requestBodyAlgorithm algorithm = requestBodyAlgorithm,
roomId = requestBodyRoomId roomId = requestBodyRoomId,
senderKey = requestBodySenderKey senderKey = requestBodySenderKey,
sessionId = requestBodySessionId sessionId = requestBodySessionId
} )
} )
} }
fun putRequestBody(requestBody: RoomKeyRequestBody?) { fun putRequestBody(requestBody: RoomKeyRequestBody?) {

View File

@ -43,12 +43,12 @@ internal open class OutgoingRoomKeyRequestEntity(
fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest { fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest {
val cancellationTxnId = this.cancellationTxnId val cancellationTxnId = this.cancellationTxnId
return OutgoingRoomKeyRequest( return OutgoingRoomKeyRequest(
RoomKeyRequestBody().apply { RoomKeyRequestBody(
algorithm = requestBodyAlgorithm algorithm = requestBodyAlgorithm,
roomId = requestBodyRoomId roomId = requestBodyRoomId,
senderKey = requestBodySenderKey senderKey = requestBodySenderKey,
sessionId = requestBodySessionId sessionId = requestBodySessionId
}, ),
getRecipients()!!, getRecipients()!!,
requestId!!, requestId!!,
OutgoingRoomKeyRequest.RequestState.from(state) OutgoingRoomKeyRequest.RequestState.from(state)

View File

@ -29,7 +29,8 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
// the list of users to get keys for. // the list of users to get keys for.
val userIds: List<String>?, val userIds: List<String>?,
// the up-to token // the up-to token
val token: String?) val token: String?
)
} }
internal class DefaultDownloadKeysForUsers @Inject constructor( internal class DefaultDownloadKeysForUsers @Inject constructor(
@ -41,13 +42,10 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty() val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty()
val body = KeysQueryBody( val body = KeysQueryBody(
deviceKeys = downloadQuery deviceKeys = downloadQuery,
token = params.token?.takeIf { it.isNotEmpty() }
) )
if (!params.token.isNullOrEmpty()) {
body.token = params.token
}
return executeRequest(eventBus) { return executeRequest(eventBus) {
apiCall = cryptoApi.downloadKeysForUsers(body) apiCall = cryptoApi.downloadKeysForUsers(body)
} }

View File

@ -83,11 +83,14 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
} }
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
// Relates to is not encrypted
val relatesToEventId = event.content.toModel<MessageRelationContent>()?.relatesTo?.eventId
if (event.senderId == userId) { if (event.senderId == userId) {
// If it's send from me, we need to keep track of Requests or Start // If it's send from me, we need to keep track of Requests or Start
// done from another device of mine // done from another device of mine
if (EventType.MESSAGE == event.type) { if (EventType.MESSAGE == event.getClearType()) {
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let { event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
@ -98,26 +101,26 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
} }
} }
} }
} else if (EventType.KEY_VERIFICATION_START == event.type) { } else if (EventType.KEY_VERIFICATION_START == event.getClearType()) {
event.getClearContent().toModel<MessageVerificationStartContent>()?.let { event.getClearContent().toModel<MessageVerificationStartContent>()?.let {
if (it.fromDevice != deviceId) { if (it.fromDevice != deviceId) {
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.verificationService.onRoomRequestHandledByOtherDevice(event) params.verificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_READY == event.type) { } else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
event.getClearContent().toModel<MessageVerificationReadyContent>()?.let { event.getClearContent().toModel<MessageVerificationReadyContent>()?.let {
if (it.fromDevice != deviceId) { if (it.fromDevice != deviceId) {
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.verificationService.onRoomRequestHandledByOtherDevice(event) params.verificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) { } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let { relatesToEventId?.let {
transactionsHandledByOtherDevice.remove(it) transactionsHandledByOtherDevice.remove(it)
params.verificationService.onRoomRequestHandledByOtherDevice(event) params.verificationService.onRoomRequestHandledByOtherDevice(event)
} }
@ -127,10 +130,9 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
return@forEach return@forEach
} }
val relatesTo = event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) {
if (relatesTo != null && transactionsHandledByOtherDevice.contains(relatesTo)) {
// Ignore this event, it is directed to another of my devices // Ignore this event, it is directed to another of my devices
Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesTo ") Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesToEventId ")
return@forEach return@forEach
} }
when (event.getClearType()) { when (event.getClearType()) {

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright (C) 2015 Square, Inc.
*
* 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 im.vector.matrix.android.internal.crypto.tools
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import kotlin.math.ceil
/**
* HMAC-based Extract-and-Expand Key Derivation Function (HkdfSha256)
* [RFC-5869] https://tools.ietf.org/html/rfc5869
*/
object HkdfSha256 {
public fun deriveSecret(inputKeyMaterial: ByteArray, salt: ByteArray?, info: ByteArray, outputLength: Int): ByteArray {
return expand(extract(salt, inputKeyMaterial), info, outputLength)
}
/**
* HkdfSha256-Extract(salt, IKM) -> PRK
*
* @param salt optional salt value (a non-secret random value);
* if not provided, it is set to a string of HashLen (size in octets) zeros.
* @param ikm input keying material
*/
private fun extract(salt: ByteArray?, ikm: ByteArray): ByteArray {
val mac = initMac(salt ?: ByteArray(HASH_LEN) { 0.toByte() })
return mac.doFinal(ikm)
}
/**
* HkdfSha256-Expand(PRK, info, L) -> OKM
*
* @param prk a pseudorandom key of at least HashLen bytes (usually, the output from the extract step)
* @param info optional context and application specific information (can be empty)
* @param outputLength length of output keying material in bytes (<= 255*HashLen)
* @return OKM output keying material
*/
private fun expand(prk: ByteArray, info: ByteArray = ByteArray(0), outputLength: Int): ByteArray {
require(outputLength <= 255 * HASH_LEN) { "outputLength must be less than or equal to 255*HashLen" }
/*
The output OKM is calculated as follows:
Notation | -> When the message is composed of several elements we use concatenation (denoted |) in the second argument;
N = ceil(L/HashLen)
T = T(1) | T(2) | T(3) | ... | T(N)
OKM = first L octets of T
where:
T(0) = empty string (zero length)
T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
...
*/
val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt()
var stepHash = ByteArray(0) // T(0) empty string (zero length)
val generatedBytes = ByteArrayOutputStream() // ByteBuffer.allocate(Math.multiplyExact(n, HASH_LEN))
val mac = initMac(prk)
for (roundNum in 1..n) {
mac.reset()
val t = ByteBuffer.allocate(stepHash.size + info.size + 1).apply {
put(stepHash)
put(info)
put(roundNum.toByte())
}
stepHash = mac.doFinal(t.array())
generatedBytes.write(stepHash)
}
return generatedBytes.toByteArray().sliceArray(0 until outputLength)
}
private fun initMac(secret: ByteArray): Mac {
val mac = Mac.getInstance(HASH_ALG)
mac.init(SecretKeySpec(secret, HASH_ALG))
return mac
}
private const val HASH_LEN = 32
private const val HASH_ALG = "HmacSHA256"
}

View File

@ -255,7 +255,7 @@ internal class DefaultVerificationService @Inject constructor(
} }
fun onRoomRequestHandledByOtherDevice(event: Event) { fun onRoomRequestHandledByOtherDevice(event: Event) {
val requestInfo = event.getClearContent().toModel<MessageRelationContent>() val requestInfo = event.content.toModel<MessageRelationContent>()
?: return ?: return
val requestId = requestInfo.relatesTo?.eventId ?: return val requestId = requestInfo.relatesTo?.eventId ?: return
getExistingVerificationRequestInRoom(event.roomId ?: "", requestId)?.let { getExistingVerificationRequestInRoom(event.roomId ?: "", requestId)?.let {
@ -465,7 +465,11 @@ internal class DefaultVerificationService @Inject constructor(
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
// If there is a corresponding request, we can auto accept // If there is a corresponding request, we can auto accept
// as we are the one requesting in first place (or we accepted the request) // as we are the one requesting in first place (or we accepted the request)
val autoAccept = getExistingVerificationRequest(otherUserId)?.any { it.transactionId == startReq.transactionID } // I need to check if the pending request was related to this device also
val autoAccept = getExistingVerificationRequest(otherUserId)?.any {
it.transactionId == startReq.transactionID
&& (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId)
}
?: false ?: false
val tx = DefaultIncomingSASDefaultVerificationTransaction( val tx = DefaultIncomingSASDefaultVerificationTransaction(
// this, // this,
@ -1083,8 +1087,12 @@ internal class DefaultVerificationService @Inject constructor(
} }
.distinct() .distinct()
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, _ -> transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, info ->
// Nothing special to do in to device mode // Nothing special to do in to device mode
updatePendingRequest(verificationRequest.copy(
// localId stays different
requestInfo = info
))
} }
requestsForUser.add(verificationRequest) requestsForUser.add(verificationRequest)

View File

@ -312,7 +312,7 @@ internal abstract class SASDefaultVerificationTransaction(
if (otherUserId == userId) { if (otherUserId == userId) {
// If me it's reasonable to sign and upload the device signature // If me it's reasonable to sign and upload the device signature
// Notice that i might not have the private keys, so may not be able to do it // Notice that i might not have the private keys, so may not be able to do it
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<Unit> { crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId") Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId")
} }

View File

@ -222,20 +222,25 @@ internal class DefaultQrCodeVerificationTransaction(
private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List<String>) { private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List<String>) {
// If not me sign his MSK and upload the signature // If not me sign his MSK and upload the signature
if (otherUserId != userId && canTrustOtherUserMasterKey) { if (canTrustOtherUserMasterKey) {
// we should trust this master key if (otherUserId != userId) {
// And check verification MSK -> SSK? // we should trust this master key
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> { // And check verification MSK -> SSK?
override fun onFailure(failure: Throwable) { crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") override fun onFailure(failure: Throwable) {
} Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId")
}) }
})
} else {
// Mark my keys as trusted locally
crossSigningService.markMyMasterKeyAsTrusted()
}
} }
if (otherUserId == userId) { if (otherUserId == userId) {
// If me it's reasonable to sign and upload the device signature // If me it's reasonable to sign and upload the device signature
// Notice that i might not have the private keys, so may not be able to do it // Notice that i might not have the private keys, so may not be able to do it
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<Unit> { crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId") Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId")
} }

View File

@ -16,19 +16,12 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor( internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) {
private val cryptoService: CryptoService,
private val timelineEventMapper: TimelineEventMapper
) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map { val tags = roomSummaryEntity.tags.map {
@ -38,21 +31,6 @@ internal class RoomSummaryMapper @Inject constructor(
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let { val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it, buildReadReceipts = false) timelineEventMapper.map(it, buildReadReceipts = false)
} }
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
// for now decrypt sync
try {
val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString())
latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
} catch (e: Throwable) {
Timber.d(e)
}
}
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,

View File

@ -17,7 +17,21 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageLocationContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.internal.network.parsing.ForceToBooleanJsonAdapter
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
@ -31,6 +45,7 @@ object MoshiProvider {
private val moshi: Moshi = Moshi.Builder() private val moshi: Moshi = Moshi.Builder()
.add(UriMoshiAdapter()) .add(UriMoshiAdapter())
.add(ForceToBooleanJsonAdapter())
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java) .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java)
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST) .registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* 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 im.vector.matrix.android.internal.network.parsing
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonQualifier
import com.squareup.moshi.JsonReader
import com.squareup.moshi.ToJson
import timber.log.Timber
@JsonQualifier
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION)
annotation class ForceToBoolean
internal class ForceToBooleanJsonAdapter {
@ToJson
fun toJson(@ForceToBoolean b: Boolean): Boolean {
return b
}
@FromJson
@ForceToBoolean
fun fromJson(reader: JsonReader): Boolean {
return when (val token = reader.peek()) {
JsonReader.Token.NUMBER -> reader.nextInt() != 0
JsonReader.Token.BOOLEAN -> reader.nextBoolean()
else -> {
Timber.e("Expecting a boolean or a int but get: $token")
reader.skipValue()
false
}
}
}
}

View File

@ -49,6 +49,7 @@ import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.sync.job.SyncWorker
@ -93,6 +94,7 @@ internal class DefaultSession @Inject constructor(
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>, private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<AccountDataService>, private val accountDataService: Lazy<AccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
private val timelineEventDecryptor: TimelineEventDecryptor,
private val shieldTrustUpdater: ShieldTrustUpdater) private val shieldTrustUpdater: ShieldTrustUpdater)
: Session, : Session,
RoomService by roomService.get(), RoomService by roomService.get(),
@ -126,6 +128,7 @@ internal class DefaultSession @Inject constructor(
isOpen = true isOpen = true
liveEntityObservers.forEach { it.start() } liveEntityObservers.forEach { it.start() }
eventBus.register(this) eventBus.register(this)
timelineEventDecryptor.start()
shieldTrustUpdater.start() shieldTrustUpdater.start()
} }
@ -163,6 +166,7 @@ internal class DefaultSession @Inject constructor(
override fun close() { override fun close() {
assert(isOpen) assert(isOpen)
stopSync() stopSync()
timelineEventDecryptor.destroy()
liveEntityObservers.forEach { it.dispose() } liveEntityObservers.forEach { it.dispose() }
cryptoService.get().close() cryptoService.get().close()
isOpen = false isOpen = false
@ -217,4 +221,9 @@ internal class DefaultSession @Inject constructor(
override fun removeListener(listener: Session.Listener) { override fun removeListener(listener: Session.Listener) {
sessionListeners.removeListener(listener) sessionListeners.removeListener(listener)
} }
// For easy debugging
override fun toString(): String {
return "$myUserId - ${sessionParams.credentials.deviceId}"
}
} }

View File

@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Filter( data class Filter(
@Json(name = "limit") var limit: Int? = null, @Json(name = "limit") val limit: Int? = null,
@Json(name = "senders") var senders: MutableList<String>? = null, @Json(name = "senders") val senders: List<String>? = null,
@Json(name = "not_senders") var notSenders: MutableList<String>? = null, @Json(name = "not_senders") val notSenders: List<String>? = null,
@Json(name = "types") var types: MutableList<String>? = null, @Json(name = "types") val types: List<String>? = null,
@Json(name = "not_types") var notTypes: MutableList<String>? = null, @Json(name = "not_types") val notTypes: List<String>? = null,
@Json(name = "rooms") var rooms: MutableList<String>? = null, @Json(name = "rooms") val rooms: List<String>? = null,
@Json(name = "not_rooms") var notRooms: MutableList<String>? = null @Json(name = "not_rooms") val notRooms: List<String>? = null
) { ) {
fun hasData(): Boolean { fun hasData(): Boolean {
return (limit != null return (limit != null

View File

@ -26,11 +26,11 @@ import im.vector.matrix.android.internal.di.MoshiProvider
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class FilterBody( internal data class FilterBody(
@Json(name = "event_fields") var eventFields: List<String>? = null, @Json(name = "event_fields") val eventFields: List<String>? = null,
@Json(name = "event_format") var eventFormat: String? = null, @Json(name = "event_format") val eventFormat: String? = null,
@Json(name = "presence") var presence: Filter? = null, @Json(name = "presence") val presence: Filter? = null,
@Json(name = "account_data") var accountData: Filter? = null, @Json(name = "account_data") val accountData: Filter? = null,
@Json(name = "room") var room: RoomFilter? = null @Json(name = "room") val room: RoomFilter? = null
) { ) {
fun toJSONString(): String { fun toJSONString(): String {

View File

@ -21,32 +21,30 @@ import im.vector.matrix.android.api.session.events.model.EventType
internal object FilterFactory { internal object FilterFactory {
fun createDefaultFilterBody(): FilterBody { fun createDefaultFilterBody(): FilterBody {
val filterBody = FilterBody() return FilterUtil.enableLazyLoading(FilterBody(), true)
FilterUtil.enableLazyLoading(filterBody, true)
return filterBody
} }
fun createRiotFilterBody(): FilterBody { fun createRiotFilterBody(): FilterBody {
val filterBody = FilterBody() return FilterBody(
filterBody.room = RoomFilter().apply { room = RoomFilter(
timeline = createRiotTimelineFilter() timeline = createRiotTimelineFilter(),
state = createRiotStateFilter() state = createRiotStateFilter()
} )
return filterBody )
} }
fun createDefaultRoomFilter(): RoomEventFilter { fun createDefaultRoomFilter(): RoomEventFilter {
return RoomEventFilter().apply { return RoomEventFilter(
lazyLoadMembers = true lazyLoadMembers = true
} )
} }
fun createRiotRoomFilter(): RoomEventFilter { fun createRiotRoomFilter(): RoomEventFilter {
return RoomEventFilter().apply { return RoomEventFilter(
lazyLoadMembers = true lazyLoadMembers = true
// TODO Enable this for optimization // TODO Enable this for optimization
// types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList()
} )
} }
private fun createRiotTimelineFilter(): RoomEventFilter { private fun createRiotTimelineFilter(): RoomEventFilter {
@ -57,9 +55,9 @@ internal object FilterFactory {
} }
private fun createRiotStateFilter(): RoomEventFilter { private fun createRiotStateFilter(): RoomEventFilter {
return RoomEventFilter().apply { return RoomEventFilter(
lazyLoadMembers = true lazyLoadMembers = true
} )
} }
// Get only managed types by Riot // Get only managed types by Riot

View File

@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class FilterResponse( data class FilterResponse(
@Json(name = "filter_id") var filterId: String @Json(name = "filter_id") val filterId: String
) )

View File

@ -21,7 +21,6 @@ internal object FilterUtil {
/** /**
* Patch the filterBody to enable or disable the data save mode * Patch the filterBody to enable or disable the data save mode
* *
*
* If data save mode is on, FilterBody will contains * If data save mode is on, FilterBody will contains
* FIXME New expected filter: * FIXME New expected filter:
* "{\"room\": {\"ephemeral\": {\"notTypes\": [\"m.typing\"]}}, \"presence\":{\"notTypes\": [\"*\"]}}" * "{\"room\": {\"ephemeral\": {\"notTypes\": [\"m.typing\"]}}, \"presence\":{\"notTypes\": [\"*\"]}}"
@ -29,6 +28,7 @@ internal object FilterUtil {
* @param filterBody filterBody to patch * @param filterBody filterBody to patch
* @param useDataSaveMode true to enable data save mode * @param useDataSaveMode true to enable data save mode
*/ */
/*
fun enableDataSaveMode(filterBody: FilterBody, useDataSaveMode: Boolean) { fun enableDataSaveMode(filterBody: FilterBody, useDataSaveMode: Boolean) {
if (useDataSaveMode) { if (useDataSaveMode) {
// Enable data save mode // Enable data save mode
@ -78,10 +78,10 @@ internal object FilterUtil {
filterBody.presence = null filterBody.presence = null
} }
} }
} } */
/** /**
* Patch the filterBody to enable or disable the lazy loading * Compute a new filterBody to enable or disable the lazy loading
* *
* *
* If lazy loading is on, the filterBody will looks like * If lazy loading is on, the filterBody will looks like
@ -90,29 +90,23 @@ internal object FilterUtil {
* @param filterBody filterBody to patch * @param filterBody filterBody to patch
* @param useLazyLoading true to enable lazy loading * @param useLazyLoading true to enable lazy loading
*/ */
fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean) { fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean): FilterBody {
if (useLazyLoading) { if (useLazyLoading) {
// Enable lazy loading // Enable lazy loading
if (filterBody.room == null) { return filterBody.copy(
filterBody.room = RoomFilter() room = filterBody.room?.copy(
} state = filterBody.room.state?.copy(lazyLoadMembers = true)
if (filterBody.room!!.state == null) { ?: RoomEventFilter(lazyLoadMembers = true)
filterBody.room!!.state = RoomEventFilter() )
} ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true))
)
filterBody.room!!.state!!.lazyLoadMembers = true
} else { } else {
if (filterBody.room != null && filterBody.room!!.state != null) { val newRoomEventFilter = filterBody.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() }
filterBody.room!!.state!!.lazyLoadMembers = null val newRoomFilter = filterBody.room?.copy(state = newRoomEventFilter)?.takeIf { it.hasData() }
if (!filterBody.room!!.state!!.hasData()) { return filterBody.copy(
filterBody.room!!.state = null room = newRoomFilter
} )
if (!filterBody.room!!.hasData()) {
filterBody.room = null
}
}
} }
} }
} }

View File

@ -26,14 +26,14 @@ import im.vector.matrix.android.internal.di.MoshiProvider
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomEventFilter( data class RoomEventFilter(
@Json(name = "limit") var limit: Int? = null, @Json(name = "limit") var limit: Int? = null,
@Json(name = "not_senders") var notSenders: MutableList<String>? = null, @Json(name = "not_senders") val notSenders: List<String>? = null,
@Json(name = "not_types") var notTypes: MutableList<String>? = null, @Json(name = "not_types") val notTypes: List<String>? = null,
@Json(name = "senders") var senders: MutableList<String>? = null, @Json(name = "senders") val senders: List<String>? = null,
@Json(name = "types") var types: MutableList<String>? = null, @Json(name = "types") val types: List<String>? = null,
@Json(name = "rooms") var rooms: MutableList<String>? = null, @Json(name = "rooms") val rooms: List<String>? = null,
@Json(name = "not_rooms") var notRooms: List<String>? = null, @Json(name = "not_rooms") val notRooms: List<String>? = null,
@Json(name = "contains_url") var containsUrl: Boolean? = null, @Json(name = "contains_url") val containsUrl: Boolean? = null,
@Json(name = "lazy_load_members") var lazyLoadMembers: Boolean? = null @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null
) { ) {
fun toJSONString(): String { fun toJSONString(): String {

View File

@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomFilter( data class RoomFilter(
@Json(name = "not_rooms") var notRooms: List<String>? = null, @Json(name = "not_rooms") val notRooms: List<String>? = null,
@Json(name = "rooms") var rooms: List<String>? = null, @Json(name = "rooms") val rooms: List<String>? = null,
@Json(name = "ephemeral") var ephemeral: RoomEventFilter? = null, @Json(name = "ephemeral") val ephemeral: RoomEventFilter? = null,
@Json(name = "include_leave") var includeLeave: Boolean? = null, @Json(name = "include_leave") val includeLeave: Boolean? = null,
@Json(name = "state") var state: RoomEventFilter? = null, @Json(name = "state") val state: RoomEventFilter? = null,
@Json(name = "timeline") var timeline: RoomEventFilter? = null, @Json(name = "timeline") val timeline: RoomEventFilter? = null,
@Json(name = "account_data") var accountData: RoomEventFilter? = null @Json(name = "account_data") val accountData: RoomEventFilter? = null
) { ) {
fun hasData(): Boolean { fun hasData(): Boolean {

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -41,17 +42,20 @@ import im.vector.matrix.android.internal.database.query.whereType
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
import im.vector.matrix.android.internal.session.sync.RoomSyncHandler import im.vector.matrix.android.internal.session.sync.RoomSyncHandler
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
import io.realm.Realm import io.realm.Realm
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryUpdater @Inject constructor( internal class RoomSummaryUpdater @Inject constructor(
@UserId private val userId: String, @UserId private val userId: String,
private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver, private val roomAvatarResolver: RoomAvatarResolver,
private val timelineEventDecryptor: Lazy<TimelineEventDecryptor>,
private val eventBus: EventBus, private val eventBus: EventBus,
private val monarchy: Monarchy) { private val monarchy: Monarchy) {
@ -141,6 +145,11 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.inviterId = null roomSummaryEntity.inviterId = null
} }
if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) {
Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
timelineEventDecryptor.get().requestDecryption(TimelineEventDecryptor.DecryptionRequest(latestPreviewableEvent.eventId, ""))
}
if (updateMembers) { if (updateMembers) {
val otherRoomMembers = RoomMemberHelper(realm, roomId) val otherRoomMembers = RoomMemberHelper(realm, roomId)
.queryRoomMembersEvent() .queryRoomMembersEvent()

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -73,11 +72,11 @@ internal class DefaultTimeline(
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val cryptoService: CryptoService,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val settings: TimelineSettings, private val settings: TimelineSettings,
private val hiddenReadReceipts: TimelineHiddenReadReceipts, private val hiddenReadReceipts: TimelineHiddenReadReceipts,
private val eventBus: EventBus private val eventBus: EventBus,
private val eventDecryptor: TimelineEventDecryptor
) : Timeline, TimelineHiddenReadReceipts.Delegate { ) : Timeline, TimelineHiddenReadReceipts.Delegate {
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>) data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
@ -114,8 +113,6 @@ internal class DefaultTimeline(
override val isLive override val isLive
get() = !hasMoreToLoad(Timeline.Direction.FORWARDS) get() = !hasMoreToLoad(Timeline.Direction.FORWARDS)
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet -> private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
if (!results.isLoaded || !results.isValid) { if (!results.isLoaded || !results.isValid) {
return@OrderedRealmCollectionChangeListener return@OrderedRealmCollectionChangeListener
@ -607,7 +604,7 @@ internal class DefaultTimeline(
if (timelineEvent.isEncrypted() if (timelineEvent.isEncrypted()
&& timelineEvent.root.mxDecryptionResult == null) { && timelineEvent.root.mxDecryptionResult == null) {
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(it, timelineID)) }
} }
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size

View File

@ -21,7 +21,6 @@ import androidx.lifecycle.Transformations
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
@ -41,7 +40,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
private val eventBus: EventBus, private val eventBus: EventBus,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val cryptoService: CryptoService, private val eventDecryptor: TimelineEventDecryptor,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
@ -60,11 +59,11 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
taskExecutor = taskExecutor, taskExecutor = taskExecutor,
contextOfEventTask = contextOfEventTask, contextOfEventTask = contextOfEventTask,
paginationTask = paginationTask, paginationTask = paginationTask,
cryptoService = cryptoService,
timelineEventMapper = timelineEventMapper, timelineEventMapper = timelineEventMapper,
settings = settings, settings = settings,
hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
eventBus = eventBus eventBus = eventBus,
eventDecryptor = eventDecryptor
) )
} }

Some files were not shown because too many files have changed in this diff Show More