Merge branch 'develop' into feature/fullscreen_avatar
This commit is contained in:
commit
d2efe0e10c
@ -12,6 +12,7 @@ Improvements 🙌:
|
||||
- Migrate to binary QR code verification (#994)
|
||||
- Share action is added to room profile and room member profile (#858)
|
||||
- Display avatar in fullscreen (#861)
|
||||
- Fix some performance issues with crypto
|
||||
|
||||
Bugfix 🐛:
|
||||
- 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)
|
||||
- Fix some invitation handling issues (#1013)
|
||||
- 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 🗣:
|
||||
-
|
||||
@ -29,6 +31,7 @@ Translations 🗣:
|
||||
SDK API changes ⚠️:
|
||||
- Get crypto methods through Session.cryptoService()
|
||||
- ProgressListener.onProgress() function will be invoked on the background thread instead of UI thread
|
||||
- Improve CreateRoomParams API (#1070)
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
@ -38,9 +38,7 @@ class AccountCreationTest : InstrumentedTest {
|
||||
fun createAccountTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
commonTestHelper.signout(session)
|
||||
|
||||
session.close()
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -50,14 +48,14 @@ class AccountCreationTest : InstrumentedTest {
|
||||
// Log again to the same account
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
|
||||
|
||||
session.close()
|
||||
session2.close()
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
commonTestHelper.signOutAndClose(session2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun simpleE2eTest() {
|
||||
val res = cryptoTestHelper.doE2ETestWithAliceInARoom()
|
||||
|
||||
res.close()
|
||||
res.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
||||
|
@ -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.session.Session
|
||||
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.model.message.MessageContent
|
||||
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.TimelineSettings
|
||||
@ -113,7 +116,7 @@ class CommonTestHelper(context: Context) {
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(nbOfMessages)
|
||||
val onEventSentListener = object : Timeline.Listener {
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
@ -122,20 +125,26 @@ class CommonTestHelper(context: Context) {
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// TODO Count only new messages?
|
||||
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
||||
sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE })
|
||||
val newMessages = snapshot
|
||||
.filter { LocalEcho.isLocalEchoId(it.eventId).not() }
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
timeline.addListener(onEventSentListener)
|
||||
timeline.start()
|
||||
timeline.addListener(timelineListener)
|
||||
for (i in 0 until nbOfMessages) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
await(latch)
|
||||
timeline.removeListener(onEventSentListener)
|
||||
timeline.removeListener(timelineListener)
|
||||
timeline.dispose()
|
||||
|
||||
// Check that all events has been created
|
||||
assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
@ -283,11 +292,10 @@ class CommonTestHelper(context: Context) {
|
||||
/**
|
||||
* Clear all provided sessions
|
||||
*/
|
||||
fun Iterable<Session>.close() = forEach { it.close() }
|
||||
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
||||
|
||||
fun signout(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
session.signOut(true, TestMatrixCallback(lock))
|
||||
await(lock)
|
||||
fun signOutAndClose(session: Session) {
|
||||
doSync<Unit> { session.signOut(true, it) }
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ data class CryptoTestData(val firstSession: Session,
|
||||
val secondSession: Session? = null,
|
||||
val thirdSession: Session? = null) {
|
||||
|
||||
fun close() {
|
||||
firstSession.close()
|
||||
secondSession?.close()
|
||||
secondSession?.close()
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
testHelper.signOutAndClose(firstSession)
|
||||
secondSession?.let { testHelper.signOutAndClose(it) }
|
||||
thirdSession?.let { testHelper.signOutAndClose(it) }
|
||||
}
|
||||
}
|
||||
|
@ -41,16 +41,15 @@ import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import java.util.Arrays
|
||||
import java.util.HashMap
|
||||
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!")
|
||||
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 messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
|
||||
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
|
||||
@ -58,34 +57,23 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
var roomId: String? = null
|
||||
val lock1 = CountDownLatch(1)
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
||||
}
|
||||
|
||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) {
|
||||
override fun onSuccess(data: String) {
|
||||
roomId = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
assertNotNull(roomId)
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
}
|
||||
|
||||
val room = aliceSession.getRoom(roomId!!)!!
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
room.enableEncryption(callback = TestMatrixCallback(lock2))
|
||||
mTestHelper.await(lock2)
|
||||
|
||||
return CryptoTestData(aliceSession, roomId!!)
|
||||
return CryptoTestData(aliceSession, roomId)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||
val statuses = HashMap<String, String>()
|
||||
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
@ -94,7 +82,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(2)
|
||||
val lock1 = CountDownLatch(1)
|
||||
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
@ -103,7 +91,6 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (t?.isNotEmpty() == true) {
|
||||
statuses["onNewRoom"] = "onNewRoom"
|
||||
lock1.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
}
|
||||
@ -114,26 +101,20 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["invite"] = "invite"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.doSync<Unit> {
|
||||
aliceRoom.invite(bobSession.myUserId, callback = it)
|
||||
}
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
|
||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
||||
|
||||
val lock2 = CountDownLatch(2)
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bobSession.getRoom(aliceRoomId)
|
||||
?.getRoomMember(aliceSession.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
statuses["AliceJoin"] = "AliceJoin"
|
||||
lock2.countDown()
|
||||
lock.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
}
|
||||
}
|
||||
@ -143,19 +124,15 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
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
|
||||
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
||||
|
||||
assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin"))
|
||||
|
||||
// bobSession.dataHandler.removeListener(bobEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
||||
}
|
||||
|
||||
@ -237,7 +214,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
var lock = CountDownLatch(1)
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val bobEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
@ -249,63 +226,35 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE }
|
||||
.size
|
||||
val messages = snapshot.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10))
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
|
||||
bobTimeline.start()
|
||||
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
|
||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
|
||||
assertTrue(results.containsKey("onToDeviceEvent"))
|
||||
// assertEquals(1, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
// Bob send 3 messages
|
||||
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])
|
||||
// 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])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(4, messagesReceivedByBobCount)
|
||||
|
||||
// Alice sends a message
|
||||
lock = CountDownLatch(2)
|
||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[1])
|
||||
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(5, messagesReceivedByBobCount)
|
||||
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
bobTimeline.dispose()
|
||||
|
||||
return cryptoTestData
|
||||
}
|
||||
@ -340,18 +289,14 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
|
||||
return MegolmBackupAuthData(
|
||||
publicKey = "abcdefg",
|
||||
signatures = HashMap<String, Map<String, String>>().apply {
|
||||
this["something"] = HashMap<String, String>().apply {
|
||||
this["ed25519:something"] = "hijklmnop"
|
||||
}
|
||||
}
|
||||
signatures = mapOf("something" to mapOf("ed25519:something" to "hijklmnop"))
|
||||
)
|
||||
}
|
||||
|
||||
fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo {
|
||||
return MegolmBackupCreationInfo().apply {
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
authData = createFakeMegolmBackupAuthData()
|
||||
}
|
||||
return MegolmBackupCreationInfo(
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
||||
authData = createFakeMegolmBackupAuthData()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,11 @@ object TestConstants {
|
||||
|
||||
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
|
||||
|
||||
// Time out to use when waiting for server response. 60s
|
||||
private const val AWAIT_TIME_OUT_MILLIS = 60000
|
||||
// Time out to use when waiting for server response. 10s
|
||||
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
|
||||
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_BOB = "Bob"
|
||||
|
@ -2,12 +2,10 @@ package im.vector.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
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.MXUsersDevicesMap
|
||||
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.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@ -34,14 +31,13 @@ class XSigningTest : InstrumentedTest {
|
||||
fun test_InitializeAndStoreKeys() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
val aliceLatch = CountDownLatch(1)
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), TestMatrixCallback(aliceLatch))
|
||||
|
||||
mTestHelper.await(aliceLatch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
val masterPubKey = myCrossSigningKeys?.masterKey()
|
||||
@ -55,7 +51,7 @@ class XSigningTest : InstrumentedTest {
|
||||
|
||||
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -74,17 +70,11 @@ class XSigningTest : InstrumentedTest {
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
val latch = CountDownLatch(2)
|
||||
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
|
||||
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
|
||||
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
|
||||
|
||||
// Check that alice can see bob keys
|
||||
val downloadLatch = CountDownLatch(1)
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch))
|
||||
mTestHelper.await(downloadLatch)
|
||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||
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())
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signout(bobSession)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -116,94 +106,56 @@ class XSigningTest : InstrumentedTest {
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
val latch = CountDownLatch(2)
|
||||
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
|
||||
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
|
||||
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
|
||||
|
||||
// Check that alice can see bob keys
|
||||
val downloadLatch = CountDownLatch(1)
|
||||
val bobUserId = bobSession.myUserId
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch))
|
||||
mTestHelper.await(downloadLatch)
|
||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||
|
||||
val trustLatch = CountDownLatch(1)
|
||||
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)
|
||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, 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
|
||||
|
||||
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
|
||||
val bobKeysLatch = CountDownLatch(1)
|
||||
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to get device")
|
||||
}
|
||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
|
||||
fail("Bob should see the new device")
|
||||
}
|
||||
bobKeysLatch.countDown()
|
||||
}
|
||||
})
|
||||
mTestHelper.await(bobKeysLatch)
|
||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||
fail("Bob should see the new device")
|
||||
}
|
||||
|
||||
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||
|
||||
// Manually mark it as trusted from first session
|
||||
val bobSignLatch = CountDownLatch(1)
|
||||
bobSession.cryptoService().crossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
bobSignLatch.countDown()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to trust bob ${failure.localizedMessage}")
|
||||
}
|
||||
})
|
||||
mTestHelper.await(bobSignLatch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
|
||||
}
|
||||
|
||||
// Now alice should cross trust bob's second device
|
||||
val aliceKeysLatch = CountDownLatch(1)
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Failed to get device")
|
||||
}
|
||||
val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||
// check that the device is seen
|
||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||
fail("Alice should see the new device")
|
||||
}
|
||||
aliceKeysLatch.countDown()
|
||||
}
|
||||
})
|
||||
mTestHelper.await(aliceKeysLatch)
|
||||
// check that the device is seen
|
||||
if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||
fail("Alice should see the new device")
|
||||
}
|
||||
|
||||
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signout(bobSession)
|
||||
mTestHelper.signout(bobSession2)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
mTestHelper.signOutAndClose(bobSession2)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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.StepProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
@ -58,7 +57,7 @@ import java.util.Collections
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
@ -103,6 +102,8 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(sessionsCount, sessions3.size)
|
||||
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,31 +121,18 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
|
||||
keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback<MegolmBackupCreationInfo> {
|
||||
override fun onSuccess(data: MegolmBackupCreationInfo) {
|
||||
assertNotNull(data)
|
||||
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, data.algorithm)
|
||||
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)
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
|
||||
assertNotNull(megolmBackupCreationInfo.authData)
|
||||
assertNotNull(megolmBackupCreationInfo.authData!!.publicKey)
|
||||
assertNotNull(megolmBackupCreationInfo.authData!!.signatures)
|
||||
assertNotNull(megolmBackupCreationInfo.recoveryKey)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
bobSession.close()
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,45 +148,22 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
|
||||
val latch = CountDownLatch(1)
|
||||
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)
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val latch2 = CountDownLatch(1)
|
||||
|
||||
// Create the version
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback<KeysVersion>(latch2) {
|
||||
override fun onSuccess(data: KeysVersion) {
|
||||
assertNotNull(data)
|
||||
assertNotNull(data.version)
|
||||
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(latch2)
|
||||
mTestHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
// Backup must be enable now
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
bobSession.close()
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,7 +203,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
KeysBackupState.ReadyToBackUp
|
||||
)
|
||||
)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,18 +224,17 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertEquals(2, nbOfKeys)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
var lastBackedUpKeysProgress = 0
|
||||
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
assertEquals(nbOfKeys, total)
|
||||
lastBackedUpKeysProgress = progress
|
||||
}
|
||||
}, TestMatrixCallback(latch))
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
assertEquals(nbOfKeys, total)
|
||||
lastBackedUpKeysProgress = progress
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
mTestHelper.await(latch)
|
||||
assertEquals(nbOfKeys, lastBackedUpKeysProgress)
|
||||
|
||||
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)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -321,7 +285,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertKeysEquals(session.exportKeys(), sessionData)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -335,25 +299,19 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
fun restoreKeysBackupAndKeyShareRequestTest() {
|
||||
fail("Check with Valere for this test. I think we do not send key share request")
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Check the SDK sent key share requests
|
||||
@ -383,23 +343,17 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(unsentRequest != null || sentRequest != null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys)
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
// - There must be no more pending key share requests
|
||||
val unsentRequestAfterRestoration = cryptoStore2
|
||||
@ -410,7 +364,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// Request is either sent or unsent
|
||||
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)
|
||||
|
||||
// - Trust the backup from the new device
|
||||
val latch = CountDownLatch(1)
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
true,
|
||||
TestMatrixCallback(latch)
|
||||
)
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
true,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
@ -453,38 +407,23 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val latch2 = CountDownLatch(1)
|
||||
var keysVersionResult: KeysVersionResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(
|
||||
object : TestMatrixCallback<KeysVersionResult?>(latch2) {
|
||||
override fun onSuccess(data: KeysVersionResult?) {
|
||||
keysVersionResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val latch3 = CountDownLatch(1)
|
||||
var keysBackupVersionTrust: KeysBackupVersionTrust? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!,
|
||||
object : TestMatrixCallback<KeysBackupVersionTrust>(latch3) {
|
||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
||||
keysBackupVersionTrust = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(latch3)
|
||||
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
|
||||
// - It must be trusted and must have 2 signatures now
|
||||
assertTrue(keysBackupVersionTrust!!.usable)
|
||||
assertEquals(2, keysBackupVersionTrust!!.signatures.size)
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
assertEquals(2, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -511,13 +450,13 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
|
||||
|
||||
// - Trust the backup from the new device with the recovery key
|
||||
val latch = CountDownLatch(1)
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
TestMatrixCallback(latch)
|
||||
)
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
@ -527,38 +466,23 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val latch2 = CountDownLatch(1)
|
||||
var keysVersionResult: KeysVersionResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(
|
||||
object : TestMatrixCallback<KeysVersionResult?>(latch2) {
|
||||
override fun onSuccess(data: KeysVersionResult?) {
|
||||
keysVersionResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val latch3 = CountDownLatch(1)
|
||||
var keysBackupVersionTrust: KeysBackupVersionTrust? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!,
|
||||
object : TestMatrixCallback<KeysBackupVersionTrust>(latch3) {
|
||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
||||
keysBackupVersionTrust = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(latch3)
|
||||
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
|
||||
// - It must be trusted and must have 2 signatures now
|
||||
assertTrue(keysBackupVersionTrust!!.usable)
|
||||
assertEquals(2, keysBackupVersionTrust!!.signatures.size)
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
assertEquals(2, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -597,7 +521,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -626,13 +550,13 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
|
||||
|
||||
// - Trust the backup from the new device with the password
|
||||
val latch = CountDownLatch(1)
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
TestMatrixCallback(latch)
|
||||
)
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
@ -642,38 +566,23 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val latch2 = CountDownLatch(1)
|
||||
var keysVersionResult: KeysVersionResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(
|
||||
object : TestMatrixCallback<KeysVersionResult?>(latch2) {
|
||||
override fun onSuccess(data: KeysVersionResult?) {
|
||||
keysVersionResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val latch3 = CountDownLatch(1)
|
||||
var keysBackupVersionTrust: KeysBackupVersionTrust? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!,
|
||||
object : TestMatrixCallback<KeysBackupVersionTrust>(latch3) {
|
||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
||||
keysBackupVersionTrust = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(latch3)
|
||||
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
|
||||
// - It must be trusted and must have 2 signatures now
|
||||
assertTrue(keysBackupVersionTrust!!.usable)
|
||||
assertEquals(2, keysBackupVersionTrust!!.signatures.size)
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
assertEquals(2, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -715,7 +624,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -748,7 +657,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -764,27 +673,21 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the password
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
val steps = ArrayList<StepProgressListener.Step>()
|
||||
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
null,
|
||||
null,
|
||||
object : StepProgressListener {
|
||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||
steps.add(step)
|
||||
}
|
||||
},
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
null,
|
||||
null,
|
||||
object : StepProgressListener {
|
||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||
steps.add(step)
|
||||
}
|
||||
},
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// Check steps
|
||||
assertEquals(105, steps.size)
|
||||
@ -807,9 +710,9 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(50, (steps[103] 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
|
||||
assertNull(importRoomKeysResult)
|
||||
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -861,25 +764,19 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the recovery key.
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
mTestHelper.await(latch2)
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
assertNull(importRoomKeysResult)
|
||||
|
||||
testData.cryptoTestData.close()
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -932,46 +829,27 @@ class KeysBackupTest : InstrumentedTest {
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Get key backup version from the home server
|
||||
var keysVersionResult: KeysVersionResult? = null
|
||||
val lock = CountDownLatch(1)
|
||||
keysBackup.getCurrentVersion(object : TestMatrixCallback<KeysVersionResult?>(lock) {
|
||||
override fun onSuccess(data: KeysVersionResult?) {
|
||||
keysVersionResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(lock)
|
||||
|
||||
assertNotNull(keysVersionResult)
|
||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||
keysBackup.getCurrentVersion(it)
|
||||
}
|
||||
|
||||
// - Check the returned KeyBackupVersion is trusted
|
||||
val latch = CountDownLatch(1)
|
||||
var keysBackupVersionTrust: KeysBackupVersionTrust? = null
|
||||
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)
|
||||
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
|
||||
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
|
||||
}
|
||||
|
||||
assertNotNull(keysBackupVersionTrust)
|
||||
assertTrue(keysBackupVersionTrust!!.usable)
|
||||
assertEquals(1, keysBackupVersionTrust!!.signatures.size)
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
assertEquals(1, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
val signature = keysBackupVersionTrust!!.signatures[0]
|
||||
val signature = keysBackupVersionTrust.signatures[0]
|
||||
assertTrue(signature.valid)
|
||||
assertNotNull(signature.device)
|
||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -983,6 +861,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
|
||||
fail("This test still fail. To investigate")
|
||||
// - Create a backup version
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
@ -1000,7 +879,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync)
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
|
||||
val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
@ -1012,12 +891,12 @@ class KeysBackupTest : InstrumentedTest {
|
||||
keysBackup2.addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
// Check the backup completes
|
||||
if (keysBackup.state == KeysBackupState.ReadyToBackUp) {
|
||||
if (newState == KeysBackupState.ReadyToBackUp) {
|
||||
count++
|
||||
|
||||
if (count == 2) {
|
||||
// Remove itself from the list of listeners
|
||||
keysBackup.removeListener(this)
|
||||
keysBackup2.removeListener(this)
|
||||
|
||||
latch.countDown()
|
||||
}
|
||||
@ -1030,7 +909,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
stateObserver2.stopAndCheckStates(null)
|
||||
aliceSession2.close()
|
||||
mTestHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1079,21 +958,17 @@ class KeysBackupTest : InstrumentedTest {
|
||||
mTestHelper.await(latch0)
|
||||
|
||||
// - Create a new backup with fake data on the homeserver, directly using the rest client
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo()
|
||||
(keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, TestMatrixCallback(latch))
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<KeysVersion> {
|
||||
(keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
// Reset the store backup status for keys
|
||||
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
|
||||
|
||||
// - Make alice back up all her keys again
|
||||
val latch2 = CountDownLatch(1)
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
}
|
||||
}, TestMatrixCallback(latch2, false))
|
||||
keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false))
|
||||
mTestHelper.await(latch2)
|
||||
|
||||
// -> That must fail and her backup state must be WrongBackUpVersion
|
||||
@ -1101,7 +976,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1129,20 +1004,14 @@ class KeysBackupTest : InstrumentedTest {
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Wait for keys backup to finish by asking again to backup keys.
|
||||
val latch = CountDownLatch(1)
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
}
|
||||
}, TestMatrixCallback(latch))
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
|
||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||
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
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
|
||||
|
||||
@ -1160,15 +1029,14 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
var isSuccessful = false
|
||||
val latch2 = CountDownLatch(1)
|
||||
keysBackup2.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
}
|
||||
}, object : TestMatrixCallback<Unit>(latch2, false) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
isSuccessful = true
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
keysBackup2.backupAllGroupSessions(
|
||||
null,
|
||||
object : TestMatrixCallback<Unit>(latch2, false) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
isSuccessful = true
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(latch2)
|
||||
|
||||
assertFalse(isSuccessful)
|
||||
@ -1178,7 +1046,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertFalse(keysBackup2.isEnabled)
|
||||
|
||||
// - 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
|
||||
val latch4 = CountDownLatch(1)
|
||||
@ -1198,17 +1066,17 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// -> It must use the same backup version
|
||||
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
|
||||
|
||||
val latch5 = CountDownLatch(1)
|
||||
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, TestMatrixCallback(latch5))
|
||||
mTestHelper.await(latch5)
|
||||
mTestHelper.doSync<Unit> {
|
||||
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
// -> It must success
|
||||
assertTrue(aliceSession2.cryptoService().keysBackupService().isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
stateObserver2.stopAndCheckStates(null)
|
||||
aliceSession2.close()
|
||||
cryptoTestData.close()
|
||||
mTestHelper.signOutAndClose(aliceSession2)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1230,18 +1098,14 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
// Delete the backup
|
||||
keysBackup.deleteBackup(keyBackupCreationInfo.version, TestMatrixCallback(latch))
|
||||
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
|
||||
|
||||
// Backup is now disabled
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
@ -1280,49 +1144,26 @@ class KeysBackupTest : InstrumentedTest {
|
||||
password: String? = null): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
|
||||
val latch = CountDownLatch(1)
|
||||
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)
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
|
||||
assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val latch2 = CountDownLatch(1)
|
||||
|
||||
// Create the version
|
||||
var version: String? = null
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback<KeysVersion>(latch2) {
|
||||
override fun onSuccess(data: KeysVersion) {
|
||||
assertNotNull(data)
|
||||
assertNotNull(data.version)
|
||||
val keysVersion = mTestHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
version = data.version
|
||||
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
mTestHelper.await(latch2)
|
||||
assertNotNull(keysVersion.version)
|
||||
|
||||
// Backup must be enable now
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
assertNotNull(version)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo!!, version!!)
|
||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
|
||||
}
|
||||
|
||||
private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||
@ -1347,7 +1188,12 @@ class KeysBackupTest : InstrumentedTest {
|
||||
private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<OlmInboundGroupSessionWrapper>,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session)
|
||||
val aliceSession2: Session) {
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common initial condition
|
||||
@ -1369,27 +1215,22 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// - Do an e2e backup to the homeserver
|
||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, TestMatrixCallback(latch))
|
||||
mTestHelper.await(latch)
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
assertEquals(2, lastProgress)
|
||||
assertEquals(2, lastTotal)
|
||||
|
||||
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
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
|
||||
|
||||
|
@ -16,26 +16,25 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.ssss
|
||||
|
||||
import android.util.Base64
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.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.SharedSecretStorageService
|
||||
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
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.secrets.DefaultSharedSecretStorageService
|
||||
import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -71,7 +70,7 @@ class QuadSTests : InstrumentedTest {
|
||||
|
||||
val TEST_KEY_ID = "my.test.Key"
|
||||
|
||||
val ssssKeyCreationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||
mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||
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)
|
||||
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
|
||||
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)
|
||||
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
|
||||
quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {})
|
||||
@ -128,7 +120,7 @@ class QuadSTests : InstrumentedTest {
|
||||
assertNotNull(defaultKeyAccountData?.content)
|
||||
assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -137,13 +129,15 @@ class QuadSTests : InstrumentedTest {
|
||||
val keyId = "My.Key"
|
||||
val info = generatedSecret(aliceSession, keyId, true)
|
||||
|
||||
val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey)
|
||||
|
||||
// Store a secret
|
||||
val clearSecret = Base64.encodeToString("42".toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val clearSecret = "42".toByteArray().toBase64NoPadding()
|
||||
mTestHelper.doSync<Unit> {
|
||||
aliceSession.sharedSecretStorageService.storeSecret(
|
||||
"secret.of.life",
|
||||
clearSecret,
|
||||
null, // default key
|
||||
listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key
|
||||
it
|
||||
)
|
||||
}
|
||||
@ -157,14 +151,13 @@ class QuadSTests : InstrumentedTest {
|
||||
val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId))
|
||||
assertNotNull(secret?.ciphertext)
|
||||
assertNotNull(secret?.mac)
|
||||
assertNotNull(secret?.ephemeral)
|
||||
assertNotNull(secret?.initializationVector)
|
||||
|
||||
// Try to decrypt??
|
||||
|
||||
val keySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(info.recoveryKey)
|
||||
|
||||
val decryptedSecret = mTestHelper.doSync<String> {
|
||||
aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
|
||||
aliceSession.sharedSecretStorageService.getSecret(
|
||||
"secret.of.life",
|
||||
null, // default key
|
||||
keySpec!!,
|
||||
it
|
||||
@ -172,7 +165,7 @@ class QuadSTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
assertEquals("Secret mismatch", clearSecret, decryptedSecret)
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -192,7 +185,7 @@ class QuadSTests : InstrumentedTest {
|
||||
quadS.setDefaultKey(TEST_KEY_ID, it)
|
||||
}
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -209,7 +202,10 @@ class QuadSTests : InstrumentedTest {
|
||||
aliceSession.sharedSecretStorageService.storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(keyId1, keyId2),
|
||||
listOf(
|
||||
SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
|
||||
SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey))
|
||||
),
|
||||
it
|
||||
)
|
||||
}
|
||||
@ -226,7 +222,7 @@ class QuadSTests : InstrumentedTest {
|
||||
mTestHelper.doSync<String> {
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId1,
|
||||
Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
|
||||
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
|
||||
it
|
||||
)
|
||||
}
|
||||
@ -234,12 +230,12 @@ class QuadSTests : InstrumentedTest {
|
||||
mTestHelper.doSync<String> {
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId2,
|
||||
Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
|
||||
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -255,7 +251,7 @@ class QuadSTests : InstrumentedTest {
|
||||
aliceSession.sharedSecretStorageService.storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(keyId1),
|
||||
listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))),
|
||||
it
|
||||
)
|
||||
}
|
||||
@ -264,7 +260,7 @@ class QuadSTests : InstrumentedTest {
|
||||
var error = false
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId1,
|
||||
Curve25519AesSha2KeySpec.fromPassphrase(
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
"A bad passphrase",
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
@ -289,7 +285,7 @@ class QuadSTests : InstrumentedTest {
|
||||
mTestHelper.doSync<String> {
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId1,
|
||||
Curve25519AesSha2KeySpec.fromPassphrase(
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
passphrase,
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
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 {
|
||||
|
@ -132,7 +132,7 @@ class SASTest : InstrumentedTest {
|
||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -189,7 +189,7 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -227,7 +227,7 @@ class SASTest : InstrumentedTest {
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -265,7 +265,7 @@ class SASTest : InstrumentedTest {
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
private fun fakeBobStart(bobSession: Session,
|
||||
@ -334,7 +334,7 @@ class SASTest : InstrumentedTest {
|
||||
mTestHelper.await(aliceCreatedLatch)
|
||||
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))
|
||||
}
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -449,7 +449,7 @@ class SASTest : InstrumentedTest {
|
||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -514,6 +514,6 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
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)
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
}
|
||||
|
@ -227,6 +227,6 @@ class VerificationTest : InstrumentedTest {
|
||||
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
|
||||
}
|
||||
|
||||
cryptoTestData.close()
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
}
|
||||
|
@ -46,13 +46,13 @@ import com.squareup.moshi.JsonClass
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnown(
|
||||
@Json(name = "m.homeserver")
|
||||
var homeServer: WellKnownBaseConfig? = null,
|
||||
val homeServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.identity_server")
|
||||
var identityServer: WellKnownBaseConfig? = null,
|
||||
val identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@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
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
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.rest.DeviceInfo
|
||||
|
||||
@ -33,7 +32,5 @@ fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||
* ========================================================================================== */
|
||||
|
||||
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
|
||||
val list = toMutableList()
|
||||
list.sortWith(DatedObjectComparators.descComparator)
|
||||
return list
|
||||
return this.sortedByDescending { it.lastSeenTs ?: 0 }
|
||||
}
|
||||
|
@ -42,6 +42,10 @@ interface CrossSigningService {
|
||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||
callback: MatrixCallback<Unit>? = null)
|
||||
|
||||
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||
uskKeyPrivateKey: String?,
|
||||
sskPrivateKey: String?) : UserTrustResult
|
||||
|
||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||
|
||||
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
||||
@ -53,11 +57,13 @@ interface CrossSigningService {
|
||||
fun trustUser(otherUserId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
fun markMyMasterKeyAsTrusted()
|
||||
|
||||
/**
|
||||
* Sign one of your devices and upload the signature
|
||||
*/
|
||||
fun signDevice(deviceId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
fun trustDevice(deviceId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
|
@ -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"
|
@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.api.session.room.model.create
|
||||
|
||||
import android.util.Patterns
|
||||
import androidx.annotation.CheckResult
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.MatrixPatterns.isUserId
|
||||
@ -120,37 +121,53 @@ data class CreateRoomParams(
|
||||
@Json(name = "power_level_content_override")
|
||||
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
|
||||
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
val contentMap = mapOf("algorithm" to algorithm)
|
||||
if (enable) {
|
||||
val contentMap = mapOf("algorithm" to algorithm)
|
||||
|
||||
val algoEvent = Event(
|
||||
type = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
content = contentMap.toContent()
|
||||
)
|
||||
val algoEvent = Event(
|
||||
type = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
content = contentMap.toContent()
|
||||
)
|
||||
|
||||
copy(
|
||||
initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent
|
||||
)
|
||||
copy(
|
||||
initialStates = newInitialStates.orEmpty() + algoEvent
|
||||
)
|
||||
} else {
|
||||
return copy(
|
||||
initialStates = newInitialStates
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Timber.e("Unsupported algorithm: $algorithm")
|
||||
this
|
||||
@ -161,7 +178,9 @@ data class CreateRoomParams(
|
||||
* Force the history visibility in the room creation parameters.
|
||||
*
|
||||
* @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 {
|
||||
// Remove the existing value if any.
|
||||
val newInitialStates = initialStates
|
||||
@ -187,7 +206,9 @@ data class CreateRoomParams(
|
||||
|
||||
/**
|
||||
* Mark as a direct message room.
|
||||
* @return a modified copy of the CreateRoomParams object
|
||||
*/
|
||||
@CheckResult
|
||||
fun setDirectMessage(): CreateRoomParams {
|
||||
return copy(
|
||||
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.
|
||||
*
|
||||
@ -217,7 +224,6 @@ data class CreateRoomParams(
|
||||
fun isDirect(): Boolean {
|
||||
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
|
||||
&& isDirect == true
|
||||
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,7 +238,9 @@ data class CreateRoomParams(
|
||||
* ids might be a matrix id or an email address.
|
||||
*
|
||||
* @param ids the participant ids to add.
|
||||
* @return a modified copy of the CreateRoomParams object
|
||||
*/
|
||||
@CheckResult
|
||||
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
||||
userId: String,
|
||||
ids: List<String>): CreateRoomParams {
|
||||
|
@ -24,7 +24,7 @@ internal data class CreateRoomResponse(
|
||||
/**
|
||||
* Required. The created room's ID.
|
||||
*/
|
||||
@Json(name = "room_id") var roomId: String
|
||||
@Json(name = "room_id") val roomId: String
|
||||
)
|
||||
|
||||
internal typealias JoinRoomResponse = CreateRoomResponse
|
||||
|
@ -27,5 +27,5 @@ data class PublicRoomsFilter(
|
||||
* A string to search for in the room metadata, e.g. name, topic, canonical alias etc. (Optional).
|
||||
*/
|
||||
@Json(name = "generic_search_term")
|
||||
var searchTerm: String? = null
|
||||
val searchTerm: String? = null
|
||||
)
|
||||
|
@ -28,30 +28,30 @@ data class PublicRoomsParams(
|
||||
* Limit the number of results returned.
|
||||
*/
|
||||
@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.
|
||||
* The direction of pagination is specified solely by which token is supplied, rather than via an explicit flag.
|
||||
*/
|
||||
@Json(name = "since")
|
||||
var since: String? = null,
|
||||
val since: String? = null,
|
||||
|
||||
/**
|
||||
* Filter to apply to the results.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "third_party_instance_id")
|
||||
var thirdPartyInstanceId: String? = null
|
||||
val thirdPartyInstanceId: String? = null
|
||||
)
|
||||
|
@ -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.
|
||||
*/
|
||||
@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
|
||||
@ -34,15 +34,15 @@ data class ThirdPartyProtocol(
|
||||
* searched before the name of a channel.
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
* 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")
|
||||
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.
|
||||
@ -51,12 +51,12 @@ data class ThirdPartyProtocol(
|
||||
* May be an empty object if no fields are defined.
|
||||
*/
|
||||
@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
|
||||
* if multiple are provided by the same application service.
|
||||
*/
|
||||
@Json(name = "instances")
|
||||
var instances: List<ThirdPartyProtocolInstance>? = null
|
||||
val instances: List<ThirdPartyProtocolInstance>? = null
|
||||
)
|
||||
|
@ -25,35 +25,35 @@ data class ThirdPartyProtocolInstance(
|
||||
* Required. A human-readable description for the protocol, such as the name.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "icon")
|
||||
var icon: String? = null,
|
||||
val icon: String? = null,
|
||||
|
||||
/**
|
||||
* Required. Preset values for fields the client may use to search by.
|
||||
*/
|
||||
@Json(name = "fields")
|
||||
var fields: Map<String, Any>? = null,
|
||||
val fields: Map<String, Any>? = null,
|
||||
|
||||
/**
|
||||
* Required. A unique identifier across all instances.
|
||||
*/
|
||||
@Json(name = "network_id")
|
||||
var networkId: String? = null,
|
||||
val networkId: String? = null,
|
||||
|
||||
/**
|
||||
* FIXDOC Not documented on matrix.org doc
|
||||
*/
|
||||
@Json(name = "instance_id")
|
||||
var instanceId: String? = null,
|
||||
val instanceId: String? = null,
|
||||
|
||||
/**
|
||||
* FIXDOC Not documented on matrix.org doc
|
||||
*/
|
||||
@Json(name = "bot_user_id")
|
||||
var botUserId: String? = null
|
||||
val botUserId: String? = null
|
||||
)
|
||||
|
@ -41,12 +41,12 @@ interface Timeline {
|
||||
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()
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|
||||
|
@ -27,6 +27,9 @@ interface TimelineService {
|
||||
/**
|
||||
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
||||
* 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 settings settings to configure the timeline.
|
||||
* @return the instantiated timeline
|
||||
|
@ -32,7 +32,8 @@ data class EncryptedSecretContent(
|
||||
/** unpadded base64-encoded ciphertext */
|
||||
@Json(name = "ciphertext") val ciphertext: 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 {
|
||||
companion object {
|
||||
/**
|
||||
|
@ -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()
|
||||
}
|
@ -27,5 +27,6 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
|
||||
|
||||
object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
|
||||
object ParsingError : SharedSecretStorageError("parsing Error")
|
||||
object BadMac : SharedSecretStorageError("Bad mac")
|
||||
data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ interface SharedSecretStorageService {
|
||||
*/
|
||||
fun generateKey(keyId: String,
|
||||
keyName: String,
|
||||
keySigner: KeySigner,
|
||||
keySigner: KeySigner?,
|
||||
callback: MatrixCallback<SsssKeyCreationInfo>)
|
||||
|
||||
/**
|
||||
@ -92,7 +92,7 @@ interface SharedSecretStorageService {
|
||||
* @param secret The secret contents.
|
||||
* @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
|
||||
@ -104,9 +104,15 @@ interface SharedSecretStorageService {
|
||||
*
|
||||
* @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 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 checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) : IntegrityResult
|
||||
|
||||
data class KeyRef(
|
||||
val keyId: String?,
|
||||
val keySpec: SsssKeySpec?
|
||||
)
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyF
|
||||
/** Tag class */
|
||||
interface SsssKeySpec
|
||||
|
||||
data class Curve25519AesSha2KeySpec(
|
||||
data class RawBytesKeySpec(
|
||||
val privateKey: ByteArray
|
||||
) : SsssKeySpec {
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): Curve25519AesSha2KeySpec {
|
||||
return Curve25519AesSha2KeySpec(
|
||||
fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): RawBytesKeySpec {
|
||||
return RawBytesKeySpec(
|
||||
privateKey = deriveKey(
|
||||
passphrase,
|
||||
salt,
|
||||
@ -40,9 +40,9 @@ data class Curve25519AesSha2KeySpec(
|
||||
)
|
||||
}
|
||||
|
||||
fun fromRecoveryKey(recoveryKey: String): Curve25519AesSha2KeySpec? {
|
||||
fun fromRecoveryKey(recoveryKey: String): RawBytesKeySpec? {
|
||||
return extractCurveKeyFromRecoveryKey(recoveryKey)?.let {
|
||||
Curve25519AesSha2KeySpec(
|
||||
RawBytesKeySpec(
|
||||
privateKey = it
|
||||
)
|
||||
}
|
||||
@ -53,7 +53,7 @@ data class Curve25519AesSha2KeySpec(
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Curve25519AesSha2KeySpec
|
||||
other as RawBytesKeySpec
|
||||
|
||||
if (!privateKey.contentEquals(other.privateKey)) return false
|
||||
|
||||
|
@ -32,20 +32,20 @@ data class RegistrationFlowResponse(
|
||||
* The list of flows.
|
||||
*/
|
||||
@Json(name = "flows")
|
||||
var flows: List<InteractiveAuthenticationFlow>? = null,
|
||||
val flows: List<InteractiveAuthenticationFlow>? = null,
|
||||
|
||||
/**
|
||||
* The list of stages the client has completed successfully.
|
||||
*/
|
||||
@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,
|
||||
* in subsequent attempts to authenticate in the same API call.
|
||||
*/
|
||||
@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.
|
||||
@ -53,7 +53,7 @@ data class RegistrationFlowResponse(
|
||||
* For example, the public key of reCAPTCHA stage could be given here.
|
||||
*/
|
||||
@Json(name = "params")
|
||||
var params: JsonDict? = null
|
||||
val params: JsonDict? = null
|
||||
|
||||
/**
|
||||
* WARNING,
|
||||
|
@ -35,6 +35,8 @@ const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-
|
||||
* Secured Shared Storage algorithm constant
|
||||
*/
|
||||
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
|
||||
const val ed25519 = "ed25519"
|
||||
|
@ -1021,12 +1021,12 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val requestBody = RoomKeyRequestBody()
|
||||
|
||||
requestBody.roomId = event.roomId
|
||||
requestBody.algorithm = wireContent["algorithm"]?.toString()
|
||||
requestBody.senderKey = wireContent["sender_key"]?.toString()
|
||||
requestBody.sessionId = wireContent["session_id"]?.toString()
|
||||
val requestBody = RoomKeyRequestBody(
|
||||
algorithm = wireContent["algorithm"]?.toString(),
|
||||
roomId = event.roomId,
|
||||
senderKey = wireContent["sender_key"]?.toString(),
|
||||
sessionId = wireContent["session_id"]?.toString()
|
||||
)
|
||||
|
||||
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
|
||||
}
|
||||
|
@ -25,54 +25,56 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
|
||||
/**
|
||||
* IncomingRoomKeyRequest class defines the incoming room keys request.
|
||||
*/
|
||||
open class IncomingRoomKeyRequest {
|
||||
/**
|
||||
* The user id
|
||||
*/
|
||||
var userId: String? = null
|
||||
data class IncomingRoomKeyRequest(
|
||||
/**
|
||||
* The user id
|
||||
*/
|
||||
override val userId: String? = null,
|
||||
|
||||
/**
|
||||
* The device id
|
||||
*/
|
||||
var deviceId: String? = null
|
||||
/**
|
||||
* The device id
|
||||
*/
|
||||
override val deviceId: String? = null,
|
||||
|
||||
/**
|
||||
* The request id
|
||||
*/
|
||||
var requestId: String? = null
|
||||
/**
|
||||
* The request id
|
||||
*/
|
||||
override val requestId: String? = null,
|
||||
|
||||
/**
|
||||
* The request body
|
||||
*/
|
||||
var requestBody: RoomKeyRequestBody? = null
|
||||
/**
|
||||
* The request body
|
||||
*/
|
||||
val requestBody: RoomKeyRequestBody? = null,
|
||||
|
||||
/**
|
||||
* The runnable to call to accept to share the keys
|
||||
*/
|
||||
@Transient
|
||||
var share: Runnable? = null
|
||||
/**
|
||||
* The runnable to call to accept to share the keys
|
||||
*/
|
||||
@Transient
|
||||
var share: Runnable? = null,
|
||||
|
||||
/**
|
||||
* The runnable to call to ignore the key share request.
|
||||
*/
|
||||
@Transient
|
||||
var ignore: Runnable? = null
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
constructor(event: Event) {
|
||||
userId = event.senderId
|
||||
val roomKeyShareRequest = event.getClearContent().toModel<RoomKeyShareRequest>()!!
|
||||
deviceId = roomKeyShareRequest.requestingDeviceId
|
||||
requestId = roomKeyShareRequest.requestId
|
||||
requestBody = if (null != roomKeyShareRequest.body) roomKeyShareRequest.body else RoomKeyRequestBody()
|
||||
/**
|
||||
* The runnable to call to ignore the key share request.
|
||||
*/
|
||||
@Transient
|
||||
var ignore: Runnable? = null
|
||||
) : IncomingRoomKeyRequestCommon {
|
||||
companion object {
|
||||
/**
|
||||
* Factory
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
fun fromEvent(event: Event): IncomingRoomKeyRequest? {
|
||||
return event.getClearContent()
|
||||
.toModel<RoomKeyShareRequest>()
|
||||
?.let {
|
||||
IncomingRoomKeyRequest(
|
||||
userId = event.senderId,
|
||||
deviceId = it.requestingDeviceId,
|
||||
requestId = it.requestId,
|
||||
requestBody = it.body ?: RoomKeyRequestBody()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for object creation from crypto store
|
||||
*/
|
||||
constructor()
|
||||
}
|
||||
|
@ -17,13 +17,44 @@
|
||||
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.toModel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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?
|
||||
}
|
@ -53,8 +53,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
fun onRoomKeyRequestEvent(event: Event) {
|
||||
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
|
||||
when (roomKeyShare?.action) {
|
||||
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
|
||||
RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
|
||||
RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) }
|
||||
RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) }
|
||||
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action ${roomKeyShare?.action}")
|
||||
}
|
||||
}
|
||||
|
@ -28,46 +28,46 @@ data class MegolmSessionData(
|
||||
* The algorithm used.
|
||||
*/
|
||||
@Json(name = "algorithm")
|
||||
var algorithm: String? = null,
|
||||
val algorithm: String? = null,
|
||||
|
||||
/**
|
||||
* Unique id for the session.
|
||||
*/
|
||||
@Json(name = "session_id")
|
||||
var sessionId: String? = null,
|
||||
val sessionId: String? = null,
|
||||
|
||||
/**
|
||||
* Sender's Curve25519 device key.
|
||||
*/
|
||||
@Json(name = "sender_key")
|
||||
var senderKey: String? = null,
|
||||
val senderKey: String? = null,
|
||||
|
||||
/**
|
||||
* Room this session is used in.
|
||||
*/
|
||||
@Json(name = "room_id")
|
||||
var roomId: String? = null,
|
||||
val roomId: String? = null,
|
||||
|
||||
/**
|
||||
* Base64'ed key data.
|
||||
*/
|
||||
@Json(name = "session_key")
|
||||
var sessionKey: String? = null,
|
||||
val sessionKey: String? = null,
|
||||
|
||||
/**
|
||||
* Other keys the sender claims.
|
||||
*/
|
||||
@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")
|
||||
// Keep it for compatibility reason.
|
||||
@Json(name = "sender_claimed_ed25519_key")
|
||||
var senderClaimedEd25519Key: String? = null,
|
||||
val senderClaimedEd25519Key: String? = null,
|
||||
|
||||
/**
|
||||
* Devices which forwarded this session to us (normally empty).
|
||||
*/
|
||||
@Json(name = "forwarding_curve25519_key_chain")
|
||||
var forwardingCurve25519KeyChain: List<String>? = null
|
||||
val forwardingCurve25519KeyChain: List<String>? = null
|
||||
)
|
||||
|
@ -213,10 +213,11 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
||||
Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody
|
||||
+ " from " + request.recipients + " id " + request.requestId)
|
||||
|
||||
val requestMessage = RoomKeyShareRequest()
|
||||
requestMessage.requestingDeviceId = cryptoStore.getDeviceId()
|
||||
requestMessage.requestId = request.requestId
|
||||
requestMessage.body = request.requestBody
|
||||
val requestMessage = RoomKeyShareRequest(
|
||||
requestingDeviceId = cryptoStore.getDeviceId(),
|
||||
requestId = request.requestId,
|
||||
body = request.requestBody
|
||||
)
|
||||
|
||||
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
|
||||
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
|
||||
@ -253,9 +254,10 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
||||
+ " to " + request.recipients
|
||||
+ " cancellation id " + request.cancellationTxnId)
|
||||
|
||||
val roomKeyShareCancellation = RoomKeyShareCancellation()
|
||||
roomKeyShareCancellation.requestingDeviceId = cryptoStore.getDeviceId()
|
||||
roomKeyShareCancellation.requestId = request.cancellationTxnId
|
||||
val roomKeyShareCancellation = RoomKeyShareCancellation(
|
||||
requestingDeviceId = cryptoStore.getDeviceId(),
|
||||
requestId = request.cancellationTxnId
|
||||
)
|
||||
|
||||
sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> {
|
||||
private fun onDone() {
|
||||
|
@ -66,12 +66,12 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
|
||||
totalNumbersOfImportedKeys++
|
||||
|
||||
// cancel any outstanding room key requests for this session
|
||||
val roomKeyRequestBody = RoomKeyRequestBody()
|
||||
|
||||
roomKeyRequestBody.algorithm = megolmSessionData.algorithm
|
||||
roomKeyRequestBody.roomId = megolmSessionData.roomId
|
||||
roomKeyRequestBody.senderKey = megolmSessionData.senderKey
|
||||
roomKeyRequestBody.sessionId = megolmSessionData.sessionId
|
||||
val roomKeyRequestBody = RoomKeyRequestBody(
|
||||
algorithm = megolmSessionData.algorithm,
|
||||
roomId = megolmSessionData.roomId,
|
||||
senderKey = megolmSessionData.senderKey,
|
||||
sessionId = megolmSessionData.sessionId
|
||||
)
|
||||
|
||||
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody)
|
||||
|
||||
@ -83,7 +83,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
|
||||
}
|
||||
|
||||
if (progressListener != null) {
|
||||
val progress = 100 * cpt / totalNumbersOfKeys
|
||||
val progress = 100 * (cpt + 1) / totalNumbersOfKeys
|
||||
|
||||
if (lastProgress != progress) {
|
||||
lastProgress = progress
|
||||
|
@ -163,12 +163,12 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
recipients.add(senderMap)
|
||||
}
|
||||
|
||||
val requestBody = RoomKeyRequestBody()
|
||||
|
||||
requestBody.roomId = event.roomId
|
||||
requestBody.algorithm = encryptedEventContent.algorithm
|
||||
requestBody.senderKey = encryptedEventContent.senderKey
|
||||
requestBody.sessionId = encryptedEventContent.sessionId
|
||||
val requestBody = RoomKeyRequestBody(
|
||||
roomId = event.roomId,
|
||||
algorithm = encryptedEventContent.algorithm,
|
||||
senderKey = encryptedEventContent.senderKey,
|
||||
sessionId = encryptedEventContent.sessionId
|
||||
)
|
||||
|
||||
outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients)
|
||||
}
|
||||
@ -264,12 +264,12 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
if (added) {
|
||||
defaultKeysBackupService.maybeBackupKeys()
|
||||
|
||||
val content = RoomKeyRequestBody()
|
||||
|
||||
content.algorithm = roomKeyContent.algorithm
|
||||
content.roomId = roomKeyContent.roomId
|
||||
content.sessionId = roomKeyContent.sessionId
|
||||
content.senderKey = senderKey
|
||||
val content = RoomKeyRequestBody(
|
||||
algorithm = roomKeyContent.algorithm,
|
||||
roomId = roomKeyContent.roomId,
|
||||
sessionId = roomKeyContent.sessionId,
|
||||
senderKey = senderKey
|
||||
)
|
||||
|
||||
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content)
|
||||
|
||||
@ -290,8 +290,8 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
|
||||
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
|
||||
val roomId = request.requestBody?.roomId ?: return false
|
||||
val senderKey = request.requestBody?.senderKey ?: return false
|
||||
val sessionId = request.requestBody?.sessionId ?: return false
|
||||
val senderKey = request.requestBody.senderKey ?: return false
|
||||
val sessionId = request.requestBody.sessionId ?: return false
|
||||
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
|
||||
}
|
||||
|
||||
@ -319,15 +319,14 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
return@mapCatching
|
||||
}
|
||||
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)
|
||||
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) }
|
||||
runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) }
|
||||
.fold(
|
||||
{
|
||||
// TODO
|
||||
payloadJson["content"] = it.exportKeys()
|
||||
?: ""
|
||||
payloadJson["content"] = it.exportKeys() ?: ""
|
||||
},
|
||||
{
|
||||
// TODO
|
||||
|
@ -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.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> {
|
||||
@ -29,14 +31,15 @@ internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncrypti
|
||||
}
|
||||
|
||||
internal class DefaultComputeTrustTask @Inject constructor(
|
||||
val cryptoStore: IMXCryptoStore
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
) : ComputeTrustTask {
|
||||
|
||||
override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel {
|
||||
override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel = withContext(coroutineDispatchers.crypto) {
|
||||
val allTrustedUserIds = params.userIds
|
||||
.filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true }
|
||||
|
||||
return if (allTrustedUserIds.isEmpty()) {
|
||||
if (allTrustedUserIds.isEmpty()) {
|
||||
RoomEncryptionTrustLevel.Default
|
||||
} else {
|
||||
// If one of the verified user as an untrusted device -> warning
|
||||
|
@ -88,7 +88,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||
Timber.i("## CrossSigning - Loading master key success")
|
||||
} else {
|
||||
Timber.w("## CrossSigning - Public master key does not match the private key")
|
||||
// TODO untrust
|
||||
pkSigning.releaseSigning()
|
||||
// TODO untrust?
|
||||
}
|
||||
}
|
||||
privateKeysInfo.user
|
||||
@ -100,7 +101,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||
Timber.i("## CrossSigning - Loading User Signing key success")
|
||||
} else {
|
||||
Timber.w("## CrossSigning - Public User key does not match the private key")
|
||||
// TODO untrust
|
||||
pkSigning.releaseSigning()
|
||||
// TODO untrust?
|
||||
}
|
||||
}
|
||||
privateKeysInfo.selfSigned
|
||||
@ -112,7 +114,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||
Timber.i("## CrossSigning - Loading Self Signing key success")
|
||||
} else {
|
||||
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 canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
|
||||
val signedDevice = selfSigningPkOlm.sign(canonicalJson)
|
||||
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also {
|
||||
it[userId] = (it[userId]
|
||||
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
|
||||
}
|
||||
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap())
|
||||
.also {
|
||||
it[userId] = (it[userId]
|
||||
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
|
||||
}
|
||||
myDevice.copy(signatures = updateSignatures).let {
|
||||
uploadSignatureQueryBuilder.withDeviceInfo(it)
|
||||
}
|
||||
|
||||
// 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()
|
||||
?: HashMap()).also {
|
||||
it[userId] = (it[userId]
|
||||
@ -292,6 +297,80 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||
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()
|
||||
|
||||
var isMaterKeyTrusted = false
|
||||
if (masterPrivateKey != null) {
|
||||
if (myMasterKey.trustLevel?.locallyVerified == true) {
|
||||
isMaterKeyTrusted = true
|
||||
} else if (masterPrivateKey != null) {
|
||||
// Check if private match public
|
||||
var olmPkSigning: OlmPkSigning? = null
|
||||
try {
|
||||
@ -507,7 +588,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||
}.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
|
||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||
if (device == null) {
|
||||
|
@ -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.RoomMemberSummaryEntityFields
|
||||
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.session.room.RoomSummaryUpdater
|
||||
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 io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -35,7 +38,7 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
private val eventBus: EventBus,
|
||||
private val computeTrustTask: ComputeTrustTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
@CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
||||
) {
|
||||
@ -44,51 +47,41 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
|
||||
}
|
||||
|
||||
private val backgroundCryptoRealm = AtomicReference<Realm>()
|
||||
private val backgroundSessionRealm = AtomicReference<Realm>()
|
||||
|
||||
// private var cryptoDevicesResult: RealmResults<DeviceInfoEntity>? = null
|
||||
|
||||
// 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 })
|
||||
// }
|
||||
// }
|
||||
private val isStarted = AtomicBoolean()
|
||||
|
||||
fun start() {
|
||||
eventBus.register(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration)
|
||||
backgroundCryptoRealm.set(cryptoRealm)
|
||||
// cryptoDevicesResult = cryptoRealm.where<DeviceInfoEntity>().findAll()
|
||||
// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener)
|
||||
|
||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
||||
if (isStarted.compareAndSet(false, true)) {
|
||||
eventBus.register(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
eventBus.unregister(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
// cryptoDevicesResult?.removeAllChangeListeners()
|
||||
backgroundCryptoRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
backgroundSessionRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
if (isStarted.compareAndSet(true, false)) {
|
||||
eventBus.unregister(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
|
||||
taskExecutor.executorScope.launch {
|
||||
if (!isStarted.get()) {
|
||||
return
|
||||
}
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
|
||||
// We need to send that back to session base
|
||||
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
||||
backgroundSessionRealm.get()?.executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
|
||||
}
|
||||
}
|
||||
@ -97,34 +90,47 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
|
||||
@Subscribe
|
||||
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
|
||||
if (!isStarted.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
onCryptoDevicesChange(update.userIds)
|
||||
}
|
||||
|
||||
private fun onCryptoDevicesChange(users: List<String>) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java)
|
||||
.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
||||
.findAll()
|
||||
.map { it.roomId }
|
||||
.distinct()
|
||||
val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java)
|
||||
?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
||||
?.findAll()
|
||||
?.map { it.roomId }
|
||||
?.distinct()
|
||||
|
||||
val map = HashMap<String, List<String>>()
|
||||
impactedRoomsId.forEach { roomId ->
|
||||
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId)
|
||||
.findAll()
|
||||
.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
impactedRoomsId?.forEach { roomId ->
|
||||
backgroundSessionRealm.get()?.let { realm ->
|
||||
RoomMemberSummaryEntity.where(realm, roomId)
|
||||
.findAll()
|
||||
.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach { entry ->
|
||||
val roomId = entry.key
|
||||
val userList = entry.value
|
||||
taskExecutor.executorScope.launch {
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
try {
|
||||
// Can throw if the crypto database has been closed in between, in this case log and ignore?
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.awaitCallback
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.olm.OlmException
|
||||
@ -167,9 +168,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
runCatching {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
val olmPkDecryption = OlmPkDecryption()
|
||||
val megolmBackupAuthData = MegolmBackupAuthData()
|
||||
|
||||
if (password != null) {
|
||||
val megolmBackupAuthData = if (password != null) {
|
||||
// Generate a private key from the password
|
||||
val backgroundProgressListener = if (progressListener == null) {
|
||||
null
|
||||
@ -188,25 +187,30 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
}
|
||||
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
|
||||
megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey)
|
||||
megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt
|
||||
megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations
|
||||
MegolmBackupAuthData(
|
||||
publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
|
||||
privateKeySalt = generatePrivateKeyResult.salt,
|
||||
privateKeyIterations = generatePrivateKeyResult.iterations
|
||||
)
|
||||
} else {
|
||||
val publicKey = olmPkDecryption.generateKey()
|
||||
|
||||
megolmBackupAuthData.publicKey = publicKey
|
||||
MegolmBackupAuthData(
|
||||
publicKey = publicKey
|
||||
)
|
||||
}
|
||||
|
||||
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.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
megolmBackupCreationInfo.authData = megolmBackupAuthData
|
||||
megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
|
||||
|
||||
megolmBackupCreationInfo
|
||||
MegolmBackupCreationInfo(
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
||||
authData = signedMegolmBackupAuthData,
|
||||
recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
|
||||
)
|
||||
}
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
@ -214,11 +218,12 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
|
||||
override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
callback: MatrixCallback<KeysVersion>) {
|
||||
val createKeysBackupVersionBody = CreateKeysBackupVersionBody()
|
||||
createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
|
||||
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
|
||||
algorithm = keysBackupCreationInfo.algorithm,
|
||||
authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
|
||||
)
|
||||
|
||||
keysBackupStateManager.state = KeysBackupState.Enabling
|
||||
|
||||
@ -229,14 +234,14 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
// Reset backup markers.
|
||||
cryptoStore.resetBackupMarkers()
|
||||
|
||||
val keyBackupVersion = KeysVersionResult()
|
||||
keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm
|
||||
keyBackupVersion.authData = createKeysBackupVersionBody.authData
|
||||
keyBackupVersion.version = data.version
|
||||
|
||||
// We can consider that the server does not have keys yet
|
||||
keyBackupVersion.count = 0
|
||||
keyBackupVersion.hash = null
|
||||
val keyBackupVersion = KeysVersionResult(
|
||||
algorithm = createKeysBackupVersionBody.algorithm,
|
||||
authData = createKeysBackupVersionBody.authData,
|
||||
version = data.version,
|
||||
// We can consider that the server does not have keys yet
|
||||
count = 0,
|
||||
hash = null
|
||||
)
|
||||
|
||||
enableKeysBackup(keyBackupVersion)
|
||||
|
||||
@ -406,7 +411,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
return keysBackupVersionTrust
|
||||
}
|
||||
|
||||
val mySigs = authData.signatures?.get(userId)
|
||||
val mySigs = authData.signatures[userId]
|
||||
if (mySigs.isNullOrEmpty()) {
|
||||
Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user")
|
||||
return keysBackupVersionTrust
|
||||
@ -469,8 +474,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
|
||||
// Get current signatures, or create an empty set
|
||||
val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap()
|
||||
?: HashMap()
|
||||
val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() ?: HashMap()
|
||||
|
||||
if (trust) {
|
||||
// Add current device signature
|
||||
@ -487,24 +491,23 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
}
|
||||
|
||||
// Create an updated version of KeysVersionResult
|
||||
val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!)
|
||||
|
||||
updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm
|
||||
|
||||
val newMegolmBackupAuthData = authData.copy()
|
||||
|
||||
val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap()
|
||||
newSignatures[userId] = myUserSignatures
|
||||
|
||||
newMegolmBackupAuthData.signatures = newSignatures
|
||||
val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy(
|
||||
signatures = newSignatures
|
||||
)
|
||||
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val adapter = moshi.adapter(Map::class.java)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map<String, Any>?
|
||||
|
||||
updateKeysBackupVersionBody
|
||||
UpdateKeysBackupVersionBody(
|
||||
algorithm = keysBackupVersion.algorithm,
|
||||
authData = adapter.fromJson(newMegolmBackupAuthDataWithNewSignature.toJsonString()) as Map<String, Any>?,
|
||||
version = keysBackupVersion.version!!)
|
||||
}
|
||||
|
||||
// And send it to the homeserver
|
||||
@ -513,13 +516,13 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// Relaunch the state machine on this updated backup version
|
||||
val newKeysBackupVersion = KeysVersionResult()
|
||||
|
||||
newKeysBackupVersion.version = keysBackupVersion.version
|
||||
newKeysBackupVersion.algorithm = keysBackupVersion.algorithm
|
||||
newKeysBackupVersion.count = keysBackupVersion.count
|
||||
newKeysBackupVersion.hash = keysBackupVersion.hash
|
||||
newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData
|
||||
val newKeysBackupVersion = KeysVersionResult(
|
||||
algorithm = keysBackupVersion.algorithm,
|
||||
authData = updateKeysBackupVersionBody.authData,
|
||||
version = keysBackupVersion.version,
|
||||
hash = keysBackupVersion.hash,
|
||||
count = keysBackupVersion.count
|
||||
)
|
||||
|
||||
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
||||
|
||||
@ -807,7 +810,10 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
// new key is sent
|
||||
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 -> {
|
||||
Timber.v("maybeBackupKeys: Skip it because state: $state")
|
||||
@ -1024,7 +1030,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
@ -1178,14 +1184,16 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
|
||||
// Gather data to send to the homeserver
|
||||
// roomId -> sessionId -> MXKeyBackupData
|
||||
val keysBackupData = KeysBackupData()
|
||||
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
|
||||
val keysBackupData = KeysBackupData(
|
||||
roomIdToRoomKeysBackupData = HashMap()
|
||||
)
|
||||
|
||||
for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) {
|
||||
val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper)
|
||||
if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) {
|
||||
val roomKeysBackupData = RoomKeysBackupData()
|
||||
roomKeysBackupData.sessionIdToKeyBackupData = HashMap()
|
||||
val roomKeysBackupData = RoomKeysBackupData(
|
||||
sessionIdToKeyBackupData = HashMap()
|
||||
)
|
||||
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData
|
||||
}
|
||||
|
||||
@ -1301,24 +1309,21 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
}
|
||||
|
||||
// Build backup data for that key
|
||||
val keyBackupData = KeyBackupData()
|
||||
try {
|
||||
keyBackupData.firstMessageIndex = olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "OlmException")
|
||||
}
|
||||
return KeyBackupData(
|
||||
firstMessageIndex = try {
|
||||
olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "OlmException")
|
||||
0L
|
||||
},
|
||||
forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size,
|
||||
isVerified = device?.isVerified == true,
|
||||
|
||||
keyBackupData.forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size
|
||||
keyBackupData.isVerified = device?.isVerified == true
|
||||
|
||||
val data = mapOf(
|
||||
"ciphertext" to encryptedSessionBackupData!!.mCipherText,
|
||||
"mac" to encryptedSessionBackupData.mMac,
|
||||
"ephemeral" to encryptedSessionBackupData.mEphemeralKey)
|
||||
|
||||
keyBackupData.sessionData = data
|
||||
|
||||
return keyBackupData
|
||||
sessionData = mapOf(
|
||||
"ciphertext" to encryptedSessionBackupData!!.mCipherText,
|
||||
"mac" to encryptedSessionBackupData.mMac,
|
||||
"ephemeral" to encryptedSessionBackupData.mEphemeralKey)
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@ -1350,8 +1355,10 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
}
|
||||
|
||||
if (sessionBackupData != null) {
|
||||
sessionBackupData.sessionId = sessionId
|
||||
sessionBackupData.roomId = roomId
|
||||
sessionBackupData = sessionBackupData.copy(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1370,11 +1377,12 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||
@VisibleForTesting
|
||||
fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
callback: MatrixCallback<KeysVersion>) {
|
||||
val createKeysBackupVersionBody = CreateKeysBackupVersionBody()
|
||||
createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
|
||||
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
|
||||
algorithm = keysBackupCreationInfo.algorithm,
|
||||
authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
|
||||
)
|
||||
|
||||
createKeysBackupVersionTask
|
||||
.configureWith(createKeysBackupVersionBody) {
|
||||
|
@ -30,26 +30,27 @@ data class MegolmBackupAuthData(
|
||||
* The curve25519 public key used to encrypt the backups.
|
||||
*/
|
||||
@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
|
||||
* private key.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "private_key_iterations")
|
||||
var privateKeyIterations: Int? = null,
|
||||
val privateKeyIterations: Int? = null,
|
||||
|
||||
/**
|
||||
* Signatures of the public key.
|
||||
* 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 {
|
||||
|
@ -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
|
||||
*/
|
||||
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].
|
||||
*/
|
||||
var algorithm: String = ""
|
||||
/**
|
||||
* Authentication data.
|
||||
*/
|
||||
val authData: MegolmBackupAuthData? = null,
|
||||
|
||||
/**
|
||||
* Authentication data.
|
||||
*/
|
||||
var authData: MegolmBackupAuthData? = null
|
||||
|
||||
/**
|
||||
* The Base58 recovery key.
|
||||
*/
|
||||
var recoveryKey: String = ""
|
||||
}
|
||||
/**
|
||||
* The Base58 recovery key.
|
||||
*/
|
||||
val recoveryKey: String = ""
|
||||
)
|
||||
|
@ -16,7 +16,21 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@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
|
||||
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.network.parsing.ForceToBoolean
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Json(name = "first_message_index")
|
||||
var firstMessageIndex: Long = 0,
|
||||
val firstMessageIndex: Long = 0,
|
||||
|
||||
/**
|
||||
* Required. The number of times this key has been forwarded.
|
||||
*/
|
||||
@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.
|
||||
* Force to boolean because of https://github.com/matrix-org/synapse/issues/6977
|
||||
*/
|
||||
@ForceToBoolean
|
||||
@Json(name = "is_verified")
|
||||
var isVerified: Boolean = false,
|
||||
val isVerified: Boolean = false,
|
||||
|
||||
/**
|
||||
* Algorithm-dependent data.
|
||||
*/
|
||||
@Json(name = "session_data")
|
||||
var sessionData: Map<String, Any>? = null
|
||||
val sessionData: Map<String, Any>? = null
|
||||
) {
|
||||
|
||||
fun toJsonString(): String {
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
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.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
@ -38,19 +37,17 @@ import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
open class KeysAlgorithmAndData {
|
||||
interface KeysAlgorithmAndData {
|
||||
|
||||
/**
|
||||
* The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
|
||||
*/
|
||||
@Json(name = "algorithm")
|
||||
var algorithm: String? = null
|
||||
val algorithm: String?
|
||||
|
||||
/**
|
||||
* algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData]
|
||||
*/
|
||||
@Json(name = "auth_data")
|
||||
var authData: JsonDict? = null
|
||||
val authData: JsonDict?
|
||||
|
||||
/**
|
||||
* Facility method to convert authData to a MegolmBackupAuthData object
|
||||
|
@ -24,9 +24,7 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class KeysBackupData(
|
||||
|
||||
// the keys are the room IDs, and the values are RoomKeysBackupData
|
||||
@Json(name = "rooms")
|
||||
var roomIdToRoomKeysBackupData: MutableMap<String, RoomKeysBackupData> = HashMap()
|
||||
|
||||
val roomIdToRoomKeysBackupData: MutableMap<String, RoomKeysBackupData> = HashMap()
|
||||
)
|
||||
|
@ -16,16 +16,33 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
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
|
||||
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
|
||||
var hash: String? = null,
|
||||
@Json(name = "hash")
|
||||
val hash: String? = null,
|
||||
|
||||
// The number of keys stored in the backup.
|
||||
var count: Int? = null
|
||||
) : KeysAlgorithmAndData()
|
||||
@Json(name = "count")
|
||||
val count: Int? = null
|
||||
) : KeysAlgorithmAndData
|
||||
|
@ -24,8 +24,7 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomKeysBackupData(
|
||||
|
||||
// the keys are the session IDs, and the values are KeyBackupData
|
||||
@Json(name = "sessions")
|
||||
var sessionIdToKeyBackupData: MutableMap<String, KeyBackupData> = HashMap()
|
||||
val sessionIdToKeyBackupData: MutableMap<String, KeyBackupData> = HashMap()
|
||||
)
|
||||
|
@ -16,10 +16,25 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
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
|
||||
@Json(name = "version")
|
||||
val version: String
|
||||
) : KeysAlgorithmAndData()
|
||||
) : KeysAlgorithmAndData
|
||||
|
@ -28,10 +28,6 @@ data class CryptoDeviceInfo(
|
||||
override val keys: Map<String, String>? = null,
|
||||
override val signatures: Map<String, Map<String, String>>? = 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 isBlocked: Boolean = false
|
||||
) : CryptoInfo {
|
||||
@ -75,19 +71,6 @@ data class CryptoDeviceInfo(
|
||||
keys?.let { map["keys"] = it }
|
||||
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 {
|
||||
|
@ -26,48 +26,47 @@ import java.io.Serializable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MXDeviceInfo(
|
||||
|
||||
/**
|
||||
* The id of this device.
|
||||
*/
|
||||
@Json(name = "device_id")
|
||||
var deviceId: String,
|
||||
val deviceId: String,
|
||||
|
||||
/**
|
||||
* the user id
|
||||
*/
|
||||
@Json(name = "user_id")
|
||||
var userId: String,
|
||||
val userId: String,
|
||||
|
||||
/**
|
||||
* The list of algorithms supported by this device.
|
||||
*/
|
||||
@Json(name = "algorithms")
|
||||
var algorithms: List<String>? = null,
|
||||
val algorithms: List<String>? = null,
|
||||
|
||||
/**
|
||||
* A map from "<key type>:<deviceId>" to "<base64-encoded key>".
|
||||
*/
|
||||
@Json(name = "keys")
|
||||
var keys: Map<String, String>? = null,
|
||||
val keys: Map<String, String>? = null,
|
||||
|
||||
/**
|
||||
* The signature of this MXDeviceInfo.
|
||||
* A map from "<userId>" to a map from "<key type>:<deviceId>" to "<signature>"
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "unsigned")
|
||||
var unsigned: JsonDict? = null,
|
||||
val unsigned: JsonDict? = null,
|
||||
|
||||
/**
|
||||
* Verification state of this device.
|
||||
*/
|
||||
var verified: Int = DEVICE_VERIFICATION_UNKNOWN
|
||||
val verified: Int = DEVICE_VERIFICATION_UNKNOWN
|
||||
) : Serializable {
|
||||
/**
|
||||
* Tells if the device is unknown
|
||||
@ -137,11 +136,11 @@ data class MXDeviceInfo(
|
||||
map["user_id"] = userId
|
||||
|
||||
if (null != algorithms) {
|
||||
map["algorithms"] = algorithms!!
|
||||
map["algorithms"] = algorithms
|
||||
}
|
||||
|
||||
if (null != keys) {
|
||||
map["keys"] = keys!!
|
||||
map["keys"] = keys
|
||||
}
|
||||
|
||||
return map
|
||||
|
@ -116,16 +116,16 @@ class OlmInboundGroupSessionWrapper : Serializable {
|
||||
return null
|
||||
}
|
||||
|
||||
MegolmSessionData().also {
|
||||
it.senderClaimedEd25519Key = keysClaimed?.get("ed25519")
|
||||
it.forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!)
|
||||
it.senderKey = senderKey
|
||||
it.senderClaimedKeys = keysClaimed
|
||||
it.roomId = roomId
|
||||
it.sessionId = olmInboundGroupSession!!.sessionIdentifier()
|
||||
it.sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex)
|
||||
it.algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
||||
}
|
||||
MegolmSessionData(
|
||||
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
|
||||
forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
|
||||
senderKey = senderKey,
|
||||
senderClaimedKeys = keysClaimed,
|
||||
roomId = roomId,
|
||||
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
|
||||
sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex),
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## export() : senderKey $senderKey failed")
|
||||
null
|
||||
|
@ -23,22 +23,21 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EncryptionEventContent(
|
||||
|
||||
/**
|
||||
* Required. The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "rotation_period_msgs")
|
||||
var rotationPeriodMsgs: Long? = null
|
||||
val rotationPeriodMsgs: Long? = null
|
||||
)
|
||||
|
@ -20,12 +20,11 @@ import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewDeviceContent(
|
||||
|
||||
// the device id
|
||||
@Json(name = "device_id")
|
||||
var deviceId: String? = null,
|
||||
val deviceId: String? = null,
|
||||
|
||||
// the room ids list
|
||||
@Json(name = "rooms")
|
||||
var rooms: List<String>? = null
|
||||
val rooms: List<String>? = null
|
||||
)
|
||||
|
@ -27,11 +27,11 @@ data class OlmEventContent(
|
||||
*
|
||||
*/
|
||||
@Json(name = "ciphertext")
|
||||
var ciphertext: Map<String, Any>? = null,
|
||||
val ciphertext: Map<String, Any>? = null,
|
||||
|
||||
/**
|
||||
* the sender key
|
||||
*/
|
||||
@Json(name = "sender_key")
|
||||
var senderKey: String? = null
|
||||
val senderKey: String? = null
|
||||
)
|
||||
|
@ -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)
|
||||
*/
|
||||
@Json(name = "user_id")
|
||||
var user_id: String? = null,
|
||||
val user_id: String? = null,
|
||||
|
||||
/**
|
||||
* The device id
|
||||
*/
|
||||
@Json(name = "device_id")
|
||||
var deviceId: String? = null,
|
||||
val deviceId: String? = null,
|
||||
|
||||
/**
|
||||
* The device display name
|
||||
*/
|
||||
@Json(name = "display_name")
|
||||
var displayName: String? = null,
|
||||
val displayName: String? = null,
|
||||
|
||||
/**
|
||||
* The last time this device has been seen.
|
||||
*/
|
||||
@Json(name = "last_seen_ts")
|
||||
var lastSeenTs: Long? = null,
|
||||
val lastSeenTs: Long? = null,
|
||||
|
||||
/**
|
||||
* The last ip address
|
||||
*/
|
||||
@Json(name = "last_seen_ip")
|
||||
var lastSeenIp: String? = null
|
||||
val lastSeenIp: String? = null
|
||||
) : DatedObject {
|
||||
|
||||
override val date: Long
|
||||
|
@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DevicesListResponse(
|
||||
@Json(name = "devices")
|
||||
var devices: List<DeviceInfo>? = null
|
||||
val devices: List<DeviceInfo>? = null
|
||||
)
|
||||
|
@ -27,38 +27,38 @@ data class EncryptedFileInfo(
|
||||
* Required. The URL to the file.
|
||||
*/
|
||||
@Json(name = "url")
|
||||
var url: String? = null,
|
||||
val url: String? = null,
|
||||
|
||||
/**
|
||||
* Not documented
|
||||
*/
|
||||
@Json(name = "mimetype")
|
||||
var mimetype: String? = null,
|
||||
val mimetype: String? = null,
|
||||
|
||||
/**
|
||||
* Required. A JSON Web Key object.
|
||||
*/
|
||||
@Json(name = "key")
|
||||
var key: EncryptedFileKey? = null,
|
||||
val key: EncryptedFileKey? = null,
|
||||
|
||||
/**
|
||||
* Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64.
|
||||
*/
|
||||
@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.
|
||||
* Clients should support the SHA-256 hash, which uses the key "sha256".
|
||||
*/
|
||||
@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".
|
||||
*/
|
||||
@Json(name = "v")
|
||||
var v: String? = null
|
||||
val v: String? = null
|
||||
) {
|
||||
/**
|
||||
* Check what the spec tells us
|
||||
|
@ -24,31 +24,31 @@ data class EncryptedFileKey(
|
||||
* Required. Algorithm. Must be "A256CTR".
|
||||
*/
|
||||
@Json(name = "alg")
|
||||
var alg: String? = null,
|
||||
val alg: String? = null,
|
||||
|
||||
/**
|
||||
* Required. Extractable. Must be true. This is a W3C extension.
|
||||
*/
|
||||
@Json(name = "ext")
|
||||
var ext: Boolean? = null,
|
||||
val ext: Boolean? = null,
|
||||
|
||||
/**
|
||||
* Required. Key operations. Must at least contain "encrypt" and "decrypt".
|
||||
*/
|
||||
@Json(name = "key_ops")
|
||||
var key_ops: List<String>? = null,
|
||||
val key_ops: List<String>? = null,
|
||||
|
||||
/**
|
||||
* Required. Key type. Must be "oct".
|
||||
*/
|
||||
@Json(name = "kty")
|
||||
var kty: String? = null,
|
||||
val kty: String? = null,
|
||||
|
||||
/**
|
||||
* Required. The key, encoded as urlsafe unpadded base64.
|
||||
*/
|
||||
@Json(name = "k")
|
||||
var k: String? = null
|
||||
val k: String? = null
|
||||
) {
|
||||
/**
|
||||
* Check what the spec tells us
|
||||
@ -62,7 +62,7 @@ data class EncryptedFileKey(
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,12 @@ import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EncryptedMessage(
|
||||
var algorithm: String? = null,
|
||||
@Json(name = "algorithm")
|
||||
val algorithm: String? = null,
|
||||
|
||||
@Json(name = "sender_key")
|
||||
var senderKey: String? = null,
|
||||
val senderKey: String? = null,
|
||||
|
||||
@Json(name = "ciphertext")
|
||||
var cipherText: Map<String, Any>? = null
|
||||
val cipherText: Map<String, Any>? = null
|
||||
) : SendToDeviceObject
|
||||
|
@ -25,9 +25,9 @@ import com.squareup.moshi.JsonClass
|
||||
internal data class KeyChangesResponse(
|
||||
// list of user ids which have new devices
|
||||
@Json(name = "changed")
|
||||
var changed: List<String>? = null,
|
||||
val changed: List<String>? = null,
|
||||
|
||||
// List of user ids who are no more tracked.
|
||||
@Json(name = "left")
|
||||
var left: List<String>? = null
|
||||
val left: List<String>? = null
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDon
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeyVerificationDone(
|
||||
@Json(name = "transaction_id") override var transactionID: String? = null
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoDone {
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
@ -27,7 +27,7 @@ internal data class KeyVerificationRequest(
|
||||
@Json(name = "from_device") override val fromDevice: String?,
|
||||
@Json(name = "methods") override val methods: List<String>,
|
||||
@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 {
|
||||
|
||||
|
@ -24,16 +24,15 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeysClaimBody(
|
||||
|
||||
/**
|
||||
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@Json(name = "one_time_keys")
|
||||
var oneTimeKeys: Map<String, Map<String, String>>
|
||||
val oneTimeKeys: Map<String, Map<String, String>>
|
||||
)
|
||||
|
@ -24,11 +24,10 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeysClaimResponse(
|
||||
|
||||
/**
|
||||
* The requested keys ordered by device by user.
|
||||
* TODO Type does not match spec, should be Map<String, JsonDict>
|
||||
*/
|
||||
@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
|
||||
)
|
||||
|
@ -25,12 +25,11 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeysQueryBody(
|
||||
|
||||
/**
|
||||
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
|
||||
*/
|
||||
@Json(name = "timeout")
|
||||
var timeout: Int? = null,
|
||||
val timeout: Int? = null,
|
||||
|
||||
/**
|
||||
* Required. The keys to be downloaded.
|
||||
@ -45,6 +44,5 @@ internal data class KeysQueryBody(
|
||||
* by the notification in that sync.
|
||||
*/
|
||||
@Json(name = "token")
|
||||
var token: String? = null
|
||||
|
||||
val token: String? = null
|
||||
)
|
||||
|
@ -23,13 +23,11 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class KeysUploadResponse(
|
||||
|
||||
/**
|
||||
* The count per algorithm as returned by the home server: a map (algorithm to count).
|
||||
*/
|
||||
@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'
|
||||
|
@ -25,14 +25,14 @@ import com.squareup.moshi.JsonClass
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomKeyRequestBody(
|
||||
@Json(name = "algorithm")
|
||||
var algorithm: String? = null,
|
||||
val algorithm: String? = null,
|
||||
|
||||
@Json(name = "room_id")
|
||||
var roomId: String? = null,
|
||||
val roomId: String? = null,
|
||||
|
||||
@Json(name = "sender_key")
|
||||
var senderKey: String? = null,
|
||||
val senderKey: String? = null,
|
||||
|
||||
@Json(name = "session_id")
|
||||
var sessionId: String? = null
|
||||
val sessionId: String? = null
|
||||
)
|
||||
|
@ -15,21 +15,17 @@
|
||||
*/
|
||||
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]
|
||||
*/
|
||||
internal open class RoomKeyShare : SendToDeviceObject {
|
||||
internal interface RoomKeyShare : SendToDeviceObject {
|
||||
|
||||
var action: String? = null
|
||||
val action: String?
|
||||
|
||||
@Json(name = "requesting_device_id")
|
||||
var requestingDeviceId: String? = null
|
||||
val requestingDeviceId: String?
|
||||
|
||||
@Json(name = "request_id")
|
||||
var requestId: String? = null
|
||||
val requestId: String?
|
||||
|
||||
companion object {
|
||||
const val ACTION_SHARE_REQUEST = "request"
|
||||
|
@ -15,14 +15,21 @@
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
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)
|
||||
internal class RoomKeyShareCancellation : RoomKeyShare() {
|
||||
init {
|
||||
action = ACTION_SHARE_CANCELLATION
|
||||
}
|
||||
}
|
||||
internal data class RoomKeyShareCancellation(
|
||||
@Json(name = "action")
|
||||
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
|
||||
|
@ -16,16 +16,23 @@
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Class representing an room key request content
|
||||
* Class representing a room key request content
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal class RoomKeyShareRequest : RoomKeyShare() {
|
||||
var body: RoomKeyRequestBody? = null
|
||||
internal data class RoomKeyShareRequest(
|
||||
@Json(name = "action")
|
||||
override val action: String? = RoomKeyShare.ACTION_SHARE_REQUEST,
|
||||
|
||||
init {
|
||||
action = ACTION_SHARE_REQUEST
|
||||
}
|
||||
}
|
||||
@Json(name = "requesting_device_id")
|
||||
override val requestingDeviceId: String? = null,
|
||||
|
||||
@Json(name = "request_id")
|
||||
override val requestId: String? = null,
|
||||
|
||||
@Json(name = "body")
|
||||
val body: RoomKeyRequestBody? = null
|
||||
) : RoomKeyShare
|
||||
|
@ -25,5 +25,5 @@ internal data class UpdateDeviceInfoBody(
|
||||
* The new display name for this device. If not given, the display name is unchanged.
|
||||
*/
|
||||
@Json(name = "display_name")
|
||||
var displayName: String? = null
|
||||
val displayName: String? = null
|
||||
)
|
||||
|
@ -17,37 +17,42 @@
|
||||
package im.vector.matrix.android.internal.crypto.secrets
|
||||
|
||||
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.session.accountdata.AccountDataService
|
||||
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.IntegrityResult
|
||||
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.KeySigner
|
||||
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
|
||||
import im.vector.matrix.android.api.session.securestorage.SsssPassphrase
|
||||
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.SharedSecretStorageError
|
||||
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.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.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.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.withOlmEncryption
|
||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
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
|
||||
|
||||
private data class Key(
|
||||
val publicKey: String,
|
||||
@Suppress("ArrayInDataClass")
|
||||
val privateKey: ByteArray
|
||||
)
|
||||
import kotlin.experimental.and
|
||||
|
||||
internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
private val accountDataService: AccountDataService,
|
||||
@ -57,14 +62,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
|
||||
override fun generateKey(keyId: String,
|
||||
keyName: String,
|
||||
keySigner: KeySigner,
|
||||
keySigner: KeySigner?,
|
||||
callback: MatrixCallback<SsssKeyCreationInfo>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||
val key = try {
|
||||
withOlmDecryption { olmPkDecryption ->
|
||||
val pubKey = olmPkDecryption.generateKey()
|
||||
val privateKey = olmPkDecryption.privateKey()
|
||||
Key(pubKey, privateKey)
|
||||
ByteArray(32).also {
|
||||
SecureRandom().nextBytes(it)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
@ -73,12 +76,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
|
||||
val storageKeyContent = SecretStorageKeyContent(
|
||||
name = keyName,
|
||||
algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2,
|
||||
passphrase = null,
|
||||
publicKey = key.publicKey
|
||||
algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2,
|
||||
passphrase = null
|
||||
)
|
||||
|
||||
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
|
||||
val signedContent = keySigner?.sign(storageKeyContent.canonicalSignable())?.let {
|
||||
storageKeyContent.copy(
|
||||
signatures = it
|
||||
)
|
||||
@ -96,7 +98,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
callback.onSuccess(SsssKeyCreationInfo(
|
||||
keyId = keyId,
|
||||
content = storageKeyContent,
|
||||
recoveryKey = computeRecoveryKey(key.privateKey)
|
||||
recoveryKey = computeRecoveryKey(key)
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -113,19 +115,9 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||
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(
|
||||
algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2,
|
||||
passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt),
|
||||
publicKey = pubKey
|
||||
algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2,
|
||||
passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt)
|
||||
)
|
||||
|
||||
val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
|
||||
@ -188,24 +180,19 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
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) {
|
||||
val encryptedContents = HashMap<String, EncryptedSecretContent>()
|
||||
try {
|
||||
if (keys.isNullOrEmpty()) {
|
||||
// use default key
|
||||
when (val key = getDefaultKey()) {
|
||||
keys.forEach {
|
||||
val keyId = it.keyId
|
||||
// encrypt the content
|
||||
when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) {
|
||||
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)
|
||||
if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) {
|
||||
encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let {
|
||||
encryptedContents[key.keyInfo.id] = it
|
||||
}
|
||||
encryptedContents[key.keyInfo.id] = EncryptedSecretContent(
|
||||
ciphertext = encryptedResult.mCipherText,
|
||||
ephemeral = encryptedResult.mEphemeralKey,
|
||||
mac = encryptedResult.mMac
|
||||
)
|
||||
} else {
|
||||
// Unknown algorithm
|
||||
callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
|
||||
@ -217,34 +204,6 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
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(
|
||||
@ -258,8 +217,109 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
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> {
|
||||
@ -299,7 +359,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
|
||||
val algorithm = key.keyInfo.content
|
||||
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)
|
||||
}
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||
@ -317,6 +377,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
}
|
||||
}.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 {
|
||||
callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: ""))
|
||||
}
|
||||
@ -327,4 +396,37 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||
const val ENCRYPTED = "encrypted"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package im.vector.matrix.android.internal.crypto.store
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
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.NewSessionListener
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
@ -382,7 +383,7 @@ internal interface IMXCryptoStore {
|
||||
*
|
||||
* @param incomingRoomKeyRequest the incoming key request
|
||||
*/
|
||||
fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest)
|
||||
fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon)
|
||||
|
||||
/**
|
||||
* Search an IncomingRoomKeyRequest
|
||||
@ -412,6 +413,8 @@ internal interface IMXCryptoStore {
|
||||
fun getLiveCrossSigningInfo(userId: String) : LiveData<Optional<MXCrossSigningInfo>>
|
||||
fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?)
|
||||
|
||||
fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean)
|
||||
|
||||
fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?)
|
||||
fun getCrossSigningPrivateKeys() : PrivateKeysInfo?
|
||||
|
||||
|
@ -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.util.Optional
|
||||
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.NewSessionListener
|
||||
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) {
|
||||
it.where<IncomingRoomKeyRequestEntity>()
|
||||
.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? {
|
||||
var existing = CrossSigningInfoEntity.get(realm, userId)
|
||||
if (info == null) {
|
||||
|
@ -32,17 +32,17 @@ internal open class IncomingRoomKeyRequestEntity(
|
||||
) : RealmObject() {
|
||||
|
||||
fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest {
|
||||
return IncomingRoomKeyRequest().also {
|
||||
it.requestId = requestId
|
||||
it.userId = userId
|
||||
it.deviceId = deviceId
|
||||
it.requestBody = RoomKeyRequestBody().apply {
|
||||
algorithm = requestBodyAlgorithm
|
||||
roomId = requestBodyRoomId
|
||||
senderKey = requestBodySenderKey
|
||||
sessionId = requestBodySessionId
|
||||
}
|
||||
}
|
||||
return IncomingRoomKeyRequest(
|
||||
requestId = requestId,
|
||||
userId = userId,
|
||||
deviceId = deviceId,
|
||||
requestBody = RoomKeyRequestBody(
|
||||
algorithm = requestBodyAlgorithm,
|
||||
roomId = requestBodyRoomId,
|
||||
senderKey = requestBodySenderKey,
|
||||
sessionId = requestBodySessionId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun putRequestBody(requestBody: RoomKeyRequestBody?) {
|
||||
|
@ -43,12 +43,12 @@ internal open class OutgoingRoomKeyRequestEntity(
|
||||
fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest {
|
||||
val cancellationTxnId = this.cancellationTxnId
|
||||
return OutgoingRoomKeyRequest(
|
||||
RoomKeyRequestBody().apply {
|
||||
algorithm = requestBodyAlgorithm
|
||||
roomId = requestBodyRoomId
|
||||
senderKey = requestBodySenderKey
|
||||
sessionId = requestBodySessionId
|
||||
},
|
||||
RoomKeyRequestBody(
|
||||
algorithm = requestBodyAlgorithm,
|
||||
roomId = requestBodyRoomId,
|
||||
senderKey = requestBodySenderKey,
|
||||
sessionId = requestBodySessionId
|
||||
),
|
||||
getRecipients()!!,
|
||||
requestId!!,
|
||||
OutgoingRoomKeyRequest.RequestState.from(state)
|
||||
|
@ -29,7 +29,8 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
|
||||
// the list of users to get keys for.
|
||||
val userIds: List<String>?,
|
||||
// the up-to token
|
||||
val token: String?)
|
||||
val token: String?
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultDownloadKeysForUsers @Inject constructor(
|
||||
@ -41,13 +42,10 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
|
||||
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty()
|
||||
|
||||
val body = KeysQueryBody(
|
||||
deviceKeys = downloadQuery
|
||||
deviceKeys = downloadQuery,
|
||||
token = params.token?.takeIf { it.isNotEmpty() }
|
||||
)
|
||||
|
||||
if (!params.token.isNullOrEmpty()) {
|
||||
body.token = params.token
|
||||
}
|
||||
|
||||
return executeRequest(eventBus) {
|
||||
apiCall = cryptoApi.downloadKeysForUsers(body)
|
||||
}
|
||||
|
@ -83,11 +83,14 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
}
|
||||
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 it's send from me, we need to keep track of Requests or Start
|
||||
// done from another device of mine
|
||||
|
||||
if (EventType.MESSAGE == event.type) {
|
||||
if (EventType.MESSAGE == event.getClearType()) {
|
||||
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
|
||||
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 {
|
||||
if (it.fromDevice != deviceId) {
|
||||
// The verification is started from another device
|
||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
|
||||
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||
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 {
|
||||
if (it.fromDevice != deviceId) {
|
||||
// The verification is started from another device
|
||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
|
||||
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||
params.verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||
}
|
||||
}
|
||||
} else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) {
|
||||
event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let {
|
||||
} else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
|
||||
relatesToEventId?.let {
|
||||
transactionsHandledByOtherDevice.remove(it)
|
||||
params.verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||
}
|
||||
@ -127,10 +130,9 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val relatesTo = event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId
|
||||
if (relatesTo != null && transactionsHandledByOtherDevice.contains(relatesTo)) {
|
||||
if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) {
|
||||
// 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
|
||||
}
|
||||
when (event.getClearType()) {
|
||||
|
@ -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"
|
||||
}
|
@ -255,7 +255,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||
}
|
||||
|
||||
fun onRoomRequestHandledByOtherDevice(event: Event) {
|
||||
val requestInfo = event.getClearContent().toModel<MessageRelationContent>()
|
||||
val requestInfo = event.content.toModel<MessageRelationContent>()
|
||||
?: return
|
||||
val requestId = requestInfo.relatesTo?.eventId ?: return
|
||||
getExistingVerificationRequestInRoom(event.roomId ?: "", requestId)?.let {
|
||||
@ -465,7 +465,11 @@ internal class DefaultVerificationService @Inject constructor(
|
||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||
// If there is a corresponding request, we can auto accept
|
||||
// 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
|
||||
val tx = DefaultIncomingSASDefaultVerificationTransaction(
|
||||
// this,
|
||||
@ -1083,8 +1087,12 @@ internal class DefaultVerificationService @Inject constructor(
|
||||
}
|
||||
.distinct()
|
||||
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, _ ->
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, info ->
|
||||
// Nothing special to do in to device mode
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
// localId stays different
|
||||
requestInfo = info
|
||||
))
|
||||
}
|
||||
|
||||
requestsForUser.add(verificationRequest)
|
||||
|
@ -312,7 +312,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||
if (otherUserId == userId) {
|
||||
// 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
|
||||
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId")
|
||||
}
|
||||
|
@ -222,20 +222,25 @@ internal class DefaultQrCodeVerificationTransaction(
|
||||
|
||||
private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List<String>) {
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (otherUserId != userId && canTrustOtherUserMasterKey) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId")
|
||||
}
|
||||
})
|
||||
if (canTrustOtherUserMasterKey) {
|
||||
if (otherUserId != userId) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
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 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
|
||||
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId")
|
||||
}
|
||||
|
@ -16,19 +16,12 @@
|
||||
|
||||
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.tag.RoomTag
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryMapper @Inject constructor(
|
||||
private val cryptoService: CryptoService,
|
||||
private val timelineEventMapper: TimelineEventMapper
|
||||
) {
|
||||
internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) {
|
||||
|
||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||
val tags = roomSummaryEntity.tags.map {
|
||||
@ -38,21 +31,6 @@ internal class RoomSummaryMapper @Inject constructor(
|
||||
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
|
||||
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(
|
||||
roomId = roomSummaryEntity.roomId,
|
||||
|
@ -17,7 +17,21 @@
|
||||
package im.vector.matrix.android.internal.di
|
||||
|
||||
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.UriMoshiAdapter
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
@ -31,6 +45,7 @@ object MoshiProvider {
|
||||
|
||||
private val moshi: Moshi = Moshi.Builder()
|
||||
.add(UriMoshiAdapter())
|
||||
.add(ForceToBooleanJsonAdapter())
|
||||
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java)
|
||||
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
|
||||
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.di.SessionId
|
||||
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.job.SyncThread
|
||||
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 accountDataService: Lazy<AccountDataService>,
|
||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
RoomService by roomService.get(),
|
||||
@ -126,6 +128,7 @@ internal class DefaultSession @Inject constructor(
|
||||
isOpen = true
|
||||
liveEntityObservers.forEach { it.start() }
|
||||
eventBus.register(this)
|
||||
timelineEventDecryptor.start()
|
||||
shieldTrustUpdater.start()
|
||||
}
|
||||
|
||||
@ -163,6 +166,7 @@ internal class DefaultSession @Inject constructor(
|
||||
override fun close() {
|
||||
assert(isOpen)
|
||||
stopSync()
|
||||
timelineEventDecryptor.destroy()
|
||||
liveEntityObservers.forEach { it.dispose() }
|
||||
cryptoService.get().close()
|
||||
isOpen = false
|
||||
@ -217,4 +221,9 @@ internal class DefaultSession @Inject constructor(
|
||||
override fun removeListener(listener: Session.Listener) {
|
||||
sessionListeners.removeListener(listener)
|
||||
}
|
||||
|
||||
// For easy debugging
|
||||
override fun toString(): String {
|
||||
return "$myUserId - ${sessionParams.credentials.deviceId}"
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Filter(
|
||||
@Json(name = "limit") var limit: Int? = null,
|
||||
@Json(name = "senders") var senders: MutableList<String>? = null,
|
||||
@Json(name = "not_senders") var notSenders: MutableList<String>? = null,
|
||||
@Json(name = "types") var types: MutableList<String>? = null,
|
||||
@Json(name = "not_types") var notTypes: MutableList<String>? = null,
|
||||
@Json(name = "rooms") var rooms: MutableList<String>? = null,
|
||||
@Json(name = "not_rooms") var notRooms: MutableList<String>? = null
|
||||
@Json(name = "limit") val limit: Int? = null,
|
||||
@Json(name = "senders") val senders: List<String>? = null,
|
||||
@Json(name = "not_senders") val notSenders: List<String>? = null,
|
||||
@Json(name = "types") val types: List<String>? = null,
|
||||
@Json(name = "not_types") val notTypes: List<String>? = null,
|
||||
@Json(name = "rooms") val rooms: List<String>? = null,
|
||||
@Json(name = "not_rooms") val notRooms: List<String>? = null
|
||||
) {
|
||||
fun hasData(): Boolean {
|
||||
return (limit != null
|
||||
|
@ -26,11 +26,11 @@ import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class FilterBody(
|
||||
@Json(name = "event_fields") var eventFields: List<String>? = null,
|
||||
@Json(name = "event_format") var eventFormat: String? = null,
|
||||
@Json(name = "presence") var presence: Filter? = null,
|
||||
@Json(name = "account_data") var accountData: Filter? = null,
|
||||
@Json(name = "room") var room: RoomFilter? = null
|
||||
@Json(name = "event_fields") val eventFields: List<String>? = null,
|
||||
@Json(name = "event_format") val eventFormat: String? = null,
|
||||
@Json(name = "presence") val presence: Filter? = null,
|
||||
@Json(name = "account_data") val accountData: Filter? = null,
|
||||
@Json(name = "room") val room: RoomFilter? = null
|
||||
) {
|
||||
|
||||
fun toJSONString(): String {
|
||||
|
@ -21,32 +21,30 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
||||
internal object FilterFactory {
|
||||
|
||||
fun createDefaultFilterBody(): FilterBody {
|
||||
val filterBody = FilterBody()
|
||||
FilterUtil.enableLazyLoading(filterBody, true)
|
||||
return filterBody
|
||||
return FilterUtil.enableLazyLoading(FilterBody(), true)
|
||||
}
|
||||
|
||||
fun createRiotFilterBody(): FilterBody {
|
||||
val filterBody = FilterBody()
|
||||
filterBody.room = RoomFilter().apply {
|
||||
timeline = createRiotTimelineFilter()
|
||||
state = createRiotStateFilter()
|
||||
}
|
||||
return filterBody
|
||||
return FilterBody(
|
||||
room = RoomFilter(
|
||||
timeline = createRiotTimelineFilter(),
|
||||
state = createRiotStateFilter()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun createDefaultRoomFilter(): RoomEventFilter {
|
||||
return RoomEventFilter().apply {
|
||||
lazyLoadMembers = true
|
||||
}
|
||||
return RoomEventFilter(
|
||||
lazyLoadMembers = true
|
||||
)
|
||||
}
|
||||
|
||||
fun createRiotRoomFilter(): RoomEventFilter {
|
||||
return RoomEventFilter().apply {
|
||||
lazyLoadMembers = true
|
||||
// TODO Enable this for optimization
|
||||
// types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList()
|
||||
}
|
||||
return RoomEventFilter(
|
||||
lazyLoadMembers = true
|
||||
// TODO Enable this for optimization
|
||||
// types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
private fun createRiotTimelineFilter(): RoomEventFilter {
|
||||
@ -57,9 +55,9 @@ internal object FilterFactory {
|
||||
}
|
||||
|
||||
private fun createRiotStateFilter(): RoomEventFilter {
|
||||
return RoomEventFilter().apply {
|
||||
lazyLoadMembers = true
|
||||
}
|
||||
return RoomEventFilter(
|
||||
lazyLoadMembers = true
|
||||
)
|
||||
}
|
||||
|
||||
// Get only managed types by Riot
|
||||
|
@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FilterResponse(
|
||||
@Json(name = "filter_id") var filterId: String
|
||||
@Json(name = "filter_id") val filterId: String
|
||||
)
|
||||
|
@ -21,7 +21,6 @@ internal object FilterUtil {
|
||||
/**
|
||||
* Patch the filterBody to enable or disable the data save mode
|
||||
*
|
||||
*
|
||||
* If data save mode is on, FilterBody will contains
|
||||
* FIXME New expected filter:
|
||||
* "{\"room\": {\"ephemeral\": {\"notTypes\": [\"m.typing\"]}}, \"presence\":{\"notTypes\": [\"*\"]}}"
|
||||
@ -29,6 +28,7 @@ internal object FilterUtil {
|
||||
* @param filterBody filterBody to patch
|
||||
* @param useDataSaveMode true to enable data save mode
|
||||
*/
|
||||
/*
|
||||
fun enableDataSaveMode(filterBody: FilterBody, useDataSaveMode: Boolean) {
|
||||
if (useDataSaveMode) {
|
||||
// Enable data save mode
|
||||
@ -78,10 +78,10 @@ internal object FilterUtil {
|
||||
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
|
||||
@ -90,29 +90,23 @@ internal object FilterUtil {
|
||||
* @param filterBody filterBody to patch
|
||||
* @param useLazyLoading true to enable lazy loading
|
||||
*/
|
||||
fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean) {
|
||||
fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean): FilterBody {
|
||||
if (useLazyLoading) {
|
||||
// Enable lazy loading
|
||||
if (filterBody.room == null) {
|
||||
filterBody.room = RoomFilter()
|
||||
}
|
||||
if (filterBody.room!!.state == null) {
|
||||
filterBody.room!!.state = RoomEventFilter()
|
||||
}
|
||||
|
||||
filterBody.room!!.state!!.lazyLoadMembers = true
|
||||
return filterBody.copy(
|
||||
room = filterBody.room?.copy(
|
||||
state = filterBody.room.state?.copy(lazyLoadMembers = true)
|
||||
?: RoomEventFilter(lazyLoadMembers = true)
|
||||
)
|
||||
?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true))
|
||||
)
|
||||
} else {
|
||||
if (filterBody.room != null && filterBody.room!!.state != null) {
|
||||
filterBody.room!!.state!!.lazyLoadMembers = null
|
||||
val newRoomEventFilter = filterBody.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() }
|
||||
val newRoomFilter = filterBody.room?.copy(state = newRoomEventFilter)?.takeIf { it.hasData() }
|
||||
|
||||
if (!filterBody.room!!.state!!.hasData()) {
|
||||
filterBody.room!!.state = null
|
||||
}
|
||||
|
||||
if (!filterBody.room!!.hasData()) {
|
||||
filterBody.room = null
|
||||
}
|
||||
}
|
||||
return filterBody.copy(
|
||||
room = newRoomFilter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,14 +26,14 @@ import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomEventFilter(
|
||||
@Json(name = "limit") var limit: Int? = null,
|
||||
@Json(name = "not_senders") var notSenders: MutableList<String>? = null,
|
||||
@Json(name = "not_types") var notTypes: MutableList<String>? = null,
|
||||
@Json(name = "senders") var senders: MutableList<String>? = null,
|
||||
@Json(name = "types") var types: MutableList<String>? = null,
|
||||
@Json(name = "rooms") var rooms: MutableList<String>? = null,
|
||||
@Json(name = "not_rooms") var notRooms: List<String>? = null,
|
||||
@Json(name = "contains_url") var containsUrl: Boolean? = null,
|
||||
@Json(name = "lazy_load_members") var lazyLoadMembers: Boolean? = null
|
||||
@Json(name = "not_senders") val notSenders: List<String>? = null,
|
||||
@Json(name = "not_types") val notTypes: List<String>? = null,
|
||||
@Json(name = "senders") val senders: List<String>? = null,
|
||||
@Json(name = "types") val types: List<String>? = null,
|
||||
@Json(name = "rooms") val rooms: List<String>? = null,
|
||||
@Json(name = "not_rooms") val notRooms: List<String>? = null,
|
||||
@Json(name = "contains_url") val containsUrl: Boolean? = null,
|
||||
@Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null
|
||||
) {
|
||||
|
||||
fun toJSONString(): String {
|
||||
|
@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomFilter(
|
||||
@Json(name = "not_rooms") var notRooms: List<String>? = null,
|
||||
@Json(name = "rooms") var rooms: List<String>? = null,
|
||||
@Json(name = "ephemeral") var ephemeral: RoomEventFilter? = null,
|
||||
@Json(name = "include_leave") var includeLeave: Boolean? = null,
|
||||
@Json(name = "state") var state: RoomEventFilter? = null,
|
||||
@Json(name = "timeline") var timeline: RoomEventFilter? = null,
|
||||
@Json(name = "account_data") var accountData: RoomEventFilter? = null
|
||||
@Json(name = "not_rooms") val notRooms: List<String>? = null,
|
||||
@Json(name = "rooms") val rooms: List<String>? = null,
|
||||
@Json(name = "ephemeral") val ephemeral: RoomEventFilter? = null,
|
||||
@Json(name = "include_leave") val includeLeave: Boolean? = null,
|
||||
@Json(name = "state") val state: RoomEventFilter? = null,
|
||||
@Json(name = "timeline") val timeline: RoomEventFilter? = null,
|
||||
@Json(name = "account_data") val accountData: RoomEventFilter? = null
|
||||
) {
|
||||
|
||||
fun hasData(): Boolean {
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.internal.session.room
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Lazy
|
||||
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.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.session.room.membership.RoomDisplayNameResolver
|
||||
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.model.RoomSyncSummary
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryUpdater @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||
private val roomAvatarResolver: RoomAvatarResolver,
|
||||
private val timelineEventDecryptor: Lazy<TimelineEventDecryptor>,
|
||||
private val eventBus: EventBus,
|
||||
private val monarchy: Monarchy) {
|
||||
|
||||
@ -141,6 +145,11 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
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) {
|
||||
val otherRoomMembers = RoomMemberHelper(realm, roomId)
|
||||
.queryRoomMembersEvent()
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.internal.session.room.timeline
|
||||
|
||||
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.RelationType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
@ -73,11 +72,11 @@ internal class DefaultTimeline(
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val contextOfEventTask: GetContextOfEventTask,
|
||||
private val paginationTask: PaginationTask,
|
||||
private val cryptoService: CryptoService,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val settings: TimelineSettings,
|
||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts,
|
||||
private val eventBus: EventBus
|
||||
private val eventBus: EventBus,
|
||||
private val eventDecryptor: TimelineEventDecryptor
|
||||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||
|
||||
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
|
||||
@ -114,8 +113,6 @@ internal class DefaultTimeline(
|
||||
override val isLive
|
||||
get() = !hasMoreToLoad(Timeline.Direction.FORWARDS)
|
||||
|
||||
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
||||
|
||||
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
|
||||
if (!results.isLoaded || !results.isValid) {
|
||||
return@OrderedRealmCollectionChangeListener
|
||||
@ -607,7 +604,7 @@ internal class DefaultTimeline(
|
||||
|
||||
if (timelineEvent.isEncrypted()
|
||||
&& 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
|
||||
|
@ -21,7 +21,6 @@ import androidx.lifecycle.Transformations
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
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.TimelineEvent
|
||||
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 taskExecutor: TaskExecutor,
|
||||
private val contextOfEventTask: GetContextOfEventTask,
|
||||
private val cryptoService: CryptoService,
|
||||
private val eventDecryptor: TimelineEventDecryptor,
|
||||
private val paginationTask: PaginationTask,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||
@ -60,11 +59,11 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
||||
taskExecutor = taskExecutor,
|
||||
contextOfEventTask = contextOfEventTask,
|
||||
paginationTask = paginationTask,
|
||||
cryptoService = cryptoService,
|
||||
timelineEventMapper = timelineEventMapper,
|
||||
settings = 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
Loading…
x
Reference in New Issue
Block a user