Merge branch 'release/0.17.0'

This commit is contained in:
Benoit Marty 2020-02-27 12:32:36 +01:00
commit 128f3493b7
444 changed files with 9815 additions and 3781 deletions

View file

@ -12,7 +12,7 @@ max_line_length=off
# Comma-separated list of rules to disable (Since 0.34.0) # Comma-separated list of rules to disable (Since 0.34.0)
# Note that rules in any ruleset other than the standard ruleset will need to be prefixed # Note that rules in any ruleset other than the standard ruleset will need to be prefixed
# by the ruleset identifier. # by the ruleset identifier.
disabled_rules=no-wildcard-imports,no-multi-spaces,colon-spacing,chain-wrapping,import-ordering,experimental:annotation disabled_rules=no-multi-spaces,colon-spacing,chain-wrapping,import-ordering,experimental:annotation
# The following (so far identified) rules are kept: # The following (so far identified) rules are kept:
# no-blank-line-before-rbrace # no-blank-line-before-rbrace
@ -30,3 +30,4 @@ disabled_rules=no-wildcard-imports,no-multi-spaces,colon-spacing,chain-wrapping,
# no-empty-class-body # no-empty-class-body
# experimental:multiline-if-else # experimental:multiline-if-else
# experimental:no-empty-first-line-in-method-block # experimental:no-empty-first-line-in-method-block
# no-wildcard-imports

View file

@ -22,6 +22,7 @@
<w>signin</w> <w>signin</w>
<w>signout</w> <w>signout</w>
<w>signup</w> <w>signup</w>
<w>ssss</w>
<w>threepid</w> <w>threepid</w>
</words> </words>
</dictionary> </dictionary>

View file

@ -1,3 +1,36 @@
Changes in RiotX 0.17.0 (2020-02-27)
===================================================
Features ✨:
- Secured Shared Storage Support (#984, #936)
- It's now possible to select several rooms (with a possible mix of clear/encrypted rooms) when sharing elements to RiotX (#1010)
- Media preview: media are previewed before being sent to a room (#1010)
- Image edition: it's now possible to edit image before sending: crop, rotate, and delete actions are supported (#1010)
- Sending image: image are sent to rooms with a reduced size. It's still possible to send original image file (#1010)
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)
- Fix crash in the room directory, when public room has no name (#1023)
- Fix restoring keys backup with passphrase (#526)
- Fix rotation of full-size image (#647)
- Fix joining rooms from directory via federation isn't working. (#808)
- 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
- Room overview shows deleted messages as “Encrypted message” (#758)
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)
Changes in RiotX 0.16.0 (2020-02-14) Changes in RiotX 0.16.0 (2020-02-14)
=================================================== ===================================================

View file

@ -34,6 +34,10 @@ allprojects {
includeGroupByRegex "com\\.github\\.jaiselrahman" includeGroupByRegex "com\\.github\\.jaiselrahman"
// And monarchy // And monarchy
includeGroupByRegex "com\\.github\\.Zhuinden" includeGroupByRegex "com\\.github\\.Zhuinden"
// And ucrop
includeGroupByRegex "com\\.github\\.yalantis"
// JsonViewer
includeGroupByRegex 'com\\.github\\.BillCarsonFr'
} }
} }
maven { maven {

View file

@ -31,6 +31,7 @@ import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
@ -94,10 +95,10 @@ class RxSession(private val session: Session) {
session.searchUsersDirectory(search, limit, excludedUserIds, it) session.searchUsersDirectory(search, limit, excludedUserIds, it)
} }
fun joinRoom(roomId: String, fun joinRoom(roomIdOrAlias: String,
reason: String? = null, reason: String? = null,
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder { viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
session.joinRoom(roomId, reason, viaServers, it) session.joinRoom(roomIdOrAlias, reason, viaServers, it)
} }
fun getRoomIdByAlias(roomAlias: String, fun getRoomIdByAlias(roomAlias: String,
@ -110,15 +111,22 @@ class RxSession(private val session: Session) {
} }
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> { fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
return session.getLiveCryptoDeviceInfo(userId).asObservable().startWithCallable { return session.cryptoService().getLiveCryptoDeviceInfo(userId).asObservable().startWithCallable {
session.getCryptoDeviceInfo(userId) session.cryptoService().getCryptoDeviceInfo(userId)
} }
} }
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> { fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable() return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asObservable()
.startWithCallable { .startWithCallable {
session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional() session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
}
}
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
return session.getLiveAccountDataEvents(types).asObservable()
.startWithCallable {
session.getAccountDataEvents(types)
} }
} }
} }

View file

@ -119,6 +119,7 @@ dependencies {
// Image // Image
implementation 'androidx.exifinterface:exifinterface:1.1.0' implementation 'androidx.exifinterface:exifinterface:1.1.0'
implementation 'id.zelory:compressor:3.0.0'
// Database // Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,10 @@
package im.vector.matrix.android.common package im.vector.matrix.android.common
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.fail
/** /**
* Compare two lists and their content * Compare two lists and their content

View file

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

View file

@ -22,7 +22,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith

View file

@ -21,7 +21,11 @@ import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import io.realm.Realm import io.realm.Realm
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith

View file

@ -17,7 +17,9 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith

View file

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

View file

@ -20,7 +20,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.common.assertByteArrayNotEqual import im.vector.matrix.android.common.assertByteArrayNotEqual
import org.junit.Assert.* import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test

View file

@ -0,0 +1,363 @@
/*
* 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.ssss
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.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_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.session.sync.model.accountdata.UserAccountDataEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
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.JVM)
class QuadSTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val emptyKeySigner = object : KeySigner {
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
return null
}
}
@Test
fun test_Generate4SKey() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val quadS = aliceSession.sharedSecretStorageService
val TEST_KEY_ID = "my.test.Key"
mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
}
// Assert Account data is updated
val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null
val liveAccountData = runBlocking(Dispatchers.Main) {
aliceSession.getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
}
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
accountData = t.getOrNull()
accountDataLock.countDown()
}
}
GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
mTestHelper.await(accountDataLock)
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_AES_HMAC_SHA2, parsed!!.algorithm)
assertEquals("Unexpected key name", "Test Key", parsed.name)
assertNull("Key was not generated from passphrase", parsed.passphrase)
// Set as default key
quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {})
var defaultKeyAccountData: UserAccountDataEvent? = null
val defaultDataLock = CountDownLatch(1)
val liveDefAccountData = runBlocking(Dispatchers.Main) {
aliceSession.getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
}
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
defaultKeyAccountData = t.getOrNull()!!
defaultDataLock.countDown()
}
}
GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) }
mTestHelper.await(defaultDataLock)
assertNotNull(defaultKeyAccountData?.content)
assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
mTestHelper.signOutAndClose(aliceSession)
}
@Test
fun test_StoreSecret() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val keyId = "My.Key"
val info = generatedSecret(aliceSession, keyId, true)
val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey)
// Store a secret
val clearSecret = "42".toByteArray().toBase64NoPadding()
mTestHelper.doSync<Unit> {
aliceSession.sharedSecretStorageService.storeSecret(
"secret.of.life",
clearSecret,
listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key
it
)
}
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *>
assertNotNull("Element should be encrypted", encryptedContent)
assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId))
assertNotNull(secret?.ciphertext)
assertNotNull(secret?.mac)
assertNotNull(secret?.initializationVector)
// Try to decrypt??
val decryptedSecret = mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret(
"secret.of.life",
null, // default key
keySpec!!,
it
)
}
assertEquals("Secret mismatch", clearSecret, decryptedSecret)
mTestHelper.signOutAndClose(aliceSession)
}
@Test
fun test_SetDefaultLocalEcho() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val quadS = aliceSession.sharedSecretStorageService
val TEST_KEY_ID = "my.test.Key"
mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
}
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
mTestHelper.doSync<Unit> {
quadS.setDefaultKey(TEST_KEY_ID, it)
}
mTestHelper.signOutAndClose(aliceSession)
}
@Test
fun test_StoreSecretWithMultipleKey() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val keyId1 = "Key.1"
val key1Info = generatedSecret(aliceSession, keyId1, true)
val keyId2 = "Key2"
val key2Info = generatedSecret(aliceSession, keyId2, true)
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
mTestHelper.doSync<Unit> {
aliceSession.sharedSecretStorageService.storeSecret(
"my.secret",
mySecretText.toByteArray().toBase64NoPadding(),
listOf(
SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey))
),
it
)
}
val accountDataEvent = aliceSession.getAccountDataEvent("my.secret")
val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
assertNotNull(encryptedContent?.get(keyId1))
assertNotNull(encryptedContent?.get(keyId2))
// Assert that can decrypt with both keys
mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
it
)
}
mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId2,
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
it
)
}
mTestHelper.signOutAndClose(aliceSession)
}
@Test
fun test_GetSecretWithBadPassphrase() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val keyId1 = "Key.1"
val passphrase = "The good pass phrase"
val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
mTestHelper.doSync<Unit> {
aliceSession.sharedSecretStorageService.storeSecret(
"my.secret",
mySecretText.toByteArray().toBase64NoPadding(),
listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))),
it
)
}
val decryptCountDownLatch = CountDownLatch(1)
var error = false
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromPassphrase(
"A bad passphrase",
key1Info.content?.passphrase?.salt ?: "",
key1Info.content?.passphrase?.iterations ?: 0,
null),
object : MatrixCallback<String> {
override fun onSuccess(data: String) {
decryptCountDownLatch.countDown()
}
override fun onFailure(failure: Throwable) {
error = true
decryptCountDownLatch.countDown()
}
}
)
mTestHelper.await(decryptCountDownLatch)
error shouldBe true
// Now try with correct key
mTestHelper.doSync<String> {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromPassphrase(
passphrase,
key1Info.content?.passphrase?.salt ?: "",
key1Info.content?.passphrase?.iterations ?: 0,
null),
it
)
}
mTestHelper.signOutAndClose(aliceSession)
}
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null
val liveAccountData = runBlocking(Dispatchers.Main) {
session.getLiveAccountDataEvent(type)
}
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == type) {
accountData = t.getOrNull()
accountDataLock.countDown()
}
}
GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
mTestHelper.await(accountDataLock)
assertNotNull("Account Data type:$type should be found", accountData)
return accountData!!
}
private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(keyId, keyId, emptyKeySigner, it)
}
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) {
mTestHelper.doSync<Unit> {
quadS.setDefaultKey(keyId, it)
}
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
}
return creationInfo
}
private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKeyWithPassphrase(
keyId,
keyId,
passphrase,
emptyKeySigner,
null,
it)
}
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) {
val setDefaultLatch = CountDownLatch(1)
quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
mTestHelper.await(setDefaultLatch)
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
}
return creationInfo
}
}

View file

@ -19,14 +19,14 @@ package im.vector.matrix.android.internal.crypto.verification
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.verification.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.verification.SasMode
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CommonTestHelper
@ -62,8 +62,8 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.getVerificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.getVerificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val bobTxCreatedLatch = CountDownLatch(1) val bobTxCreatedLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
@ -75,7 +75,7 @@ class SASTest : InstrumentedTest {
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
bobSession.myUserId, bobSession.myUserId,
bobSession.getMyDevice().deviceId, bobSession.cryptoService().getMyDevice().deviceId,
null) null)
assertNotNull("Alice should have a started transaction", txID) assertNotNull("Alice should have a started transaction", txID)
@ -132,7 +132,7 @@ class SASTest : InstrumentedTest {
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -157,7 +157,7 @@ class SASTest : InstrumentedTest {
} }
} }
} }
bobSession.getVerificationService().addListener(bobListener) bobSession.cryptoService().verificationService().addListener(bobListener)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) { // TODO override fun onToDeviceEvent(event: Event?) {
@ -172,7 +172,7 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.getMyDevice().deviceId val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
@ -181,7 +181,7 @@ class SASTest : InstrumentedTest {
} }
} }
} }
aliceSession.getVerificationService().addListener(aliceListener) aliceSession.cryptoService().verificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
@ -189,7 +189,7 @@ class SASTest : InstrumentedTest {
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -218,7 +218,7 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.getMyDevice().deviceId val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
@ -227,7 +227,7 @@ class SASTest : InstrumentedTest {
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -256,7 +256,7 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.getMyDevice().deviceId val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
@ -265,7 +265,7 @@ class SASTest : InstrumentedTest {
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
private fun fakeBobStart(bobSession: Session, private fun fakeBobStart(bobSession: Session,
@ -277,7 +277,7 @@ class SASTest : InstrumentedTest {
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS, mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) { codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
val startMessage = KeyVerificationStart( val startMessage = KeyVerificationStart(
fromDevice = bobSession.getMyDevice().deviceId, fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
method = VerificationMethod.SAS.toValue(), method = VerificationMethod.SAS.toValue(),
transactionID = tid, transactionID = tid,
keyAgreementProtocols = protocols, keyAgreementProtocols = protocols,
@ -307,7 +307,7 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.getVerificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2) val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2) val aliceCancelledLatch = CountDownLatch(2)
@ -327,14 +327,14 @@ class SASTest : InstrumentedTest {
aliceVerificationService.addListener(aliceListener) aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceCreatedLatch) mTestHelper.await(aliceCreatedLatch)
mTestHelper.await(aliceCancelledLatch) mTestHelper.await(aliceCancelledLatch)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
/** /**
@ -347,8 +347,8 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.getVerificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.getVerificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
var accepted: KeyVerificationAccept? = null var accepted: KeyVerificationAccept? = null
var startReq: KeyVerificationStart? = null var startReq: KeyVerificationStart? = null
@ -377,7 +377,7 @@ class SASTest : InstrumentedTest {
bobVerificationService.addListener(bobListener) bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceAcceptedLatch) mTestHelper.await(aliceAcceptedLatch)
@ -393,7 +393,7 @@ class SASTest : InstrumentedTest {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it)) assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
} }
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -403,8 +403,8 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.getVerificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.getVerificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1) val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
@ -438,7 +438,7 @@ class SASTest : InstrumentedTest {
bobVerificationService.addListener(bobListener) bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceSASLatch) mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch) mTestHelper.await(bobSASLatch)
@ -449,7 +449,7 @@ class SASTest : InstrumentedTest {
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
@Test @Test
@ -459,8 +459,8 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.getVerificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.getVerificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1) val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
@ -500,20 +500,20 @@ class SASTest : InstrumentedTest {
bobVerificationService.addListener(bobListener) bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceSASLatch) mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch) mTestHelper.await(bobSASLatch)
// Assert that devices are verified // Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId) val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId) val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
// latch wait a bit again // latch wait a bit again
Thread.sleep(1000) Thread.sleep(1000)
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
} }

View file

@ -0,0 +1,35 @@
/*
* 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.verification.qrcode
fun hexToByteArray(hex: String): ByteArray {
// Remove all spaces
return hex.replace(" ", "")
.let {
if (it.length % 2 != 0) "0$it" else it
}
.let {
ByteArray(it.length / 2)
.apply {
for (i in this.indices) {
val index = i * 2
val v = it.substring(index, index + 2).toInt(16)
this[i] = v.toByte()
}
}
}
}

View file

@ -0,0 +1,249 @@
/*
* 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.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import org.amshove.kluent.shouldBeNull
import org.amshove.kluent.shouldEqual
import org.amshove.kluent.shouldEqualTo
import org.amshove.kluent.shouldNotBeNull
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class QrCodeTest : InstrumentedTest {
private val qrCode1 = QrCodeData.VerifyingAnotherUser(
transactionId = "MaTransaction",
userMasterCrossSigningPublicKey = "ktEwcUP6su1xh+GuE+CYkQ3H6W/DIl+ybHFdaEOrolU",
otherUserMasterCrossSigningPublicKey = "TXluZKTZLvSRWOTPlOqLq534bA+/K4zLFKSu9cGLQaU",
sharedSecret = "MTIzNDU2Nzg"
)
private val value1 = "MATRIX\u0002\u0000\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
private val qrCode2 = QrCodeData.SelfVerifyingMasterKeyTrusted(
transactionId = "MaTransaction",
userMasterCrossSigningPublicKey = "ktEwcUP6su1xh+GuE+CYkQ3H6W/DIl+ybHFdaEOrolU",
otherDeviceKey = "TXluZKTZLvSRWOTPlOqLq534bA+/K4zLFKSu9cGLQaU",
sharedSecret = "MTIzNDU2Nzg"
)
private val value2 = "MATRIX\u0002\u0001\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
private val qrCode3 = QrCodeData.SelfVerifyingMasterKeyNotTrusted(
transactionId = "MaTransaction",
deviceKey = "TXluZKTZLvSRWOTPlOqLq534bA+/K4zLFKSu9cGLQaU",
userMasterCrossSigningPublicKey = "ktEwcUP6su1xh+GuE+CYkQ3H6W/DIl+ybHFdaEOrolU",
sharedSecret = "MTIzNDU2Nzg"
)
private val value3 = "MATRIX\u0002\u0002\u0000\u000DMaTransactionMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008B\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢U12345678"
private val sharedSecretByteArray = "12345678".toByteArray(Charsets.ISO_8859_1)
private val tlx_byteArray = hexToByteArray("4d 79 6e 64 a4 d9 2e f4 91 58 e4 cf 94 ea 8b ab 9d f8 6c 0f bf 2b 8c cb 14 a4 ae f5 c1 8b 41 a5")
private val kte_byteArray = hexToByteArray("92 d1 30 71 43 fa b2 ed 71 87 e1 ae 13 e0 98 91 0d c7 e9 6f c3 22 5f b2 6c 71 5d 68 43 ab a2 55")
@Test
fun testEncoding1() {
qrCode1.toEncodedString() shouldEqual value1
}
@Test
fun testEncoding2() {
qrCode2.toEncodedString() shouldEqual value2
}
@Test
fun testEncoding3() {
qrCode3.toEncodedString() shouldEqual value3
}
@Test
fun testSymmetry1() {
qrCode1.toEncodedString().toQrCodeData() shouldEqual qrCode1
}
@Test
fun testSymmetry2() {
qrCode2.toEncodedString().toQrCodeData() shouldEqual qrCode2
}
@Test
fun testSymmetry3() {
qrCode3.toEncodedString().toQrCodeData() shouldEqual qrCode3
}
@Test
fun testCase1() {
val url = qrCode1.toEncodedString()
val byteArray = url.toByteArray(Charsets.ISO_8859_1)
checkHeader(byteArray)
// Mode
byteArray[7] shouldEqualTo 0
checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray)
compareArray(byteArray.copyOfRange(23 + 32, 23 + 64), tlx_byteArray)
compareArray(byteArray.copyOfRange(23 + 64, byteArray.size), sharedSecretByteArray)
}
@Test
fun testCase2() {
val url = qrCode2.toEncodedString()
val byteArray = url.toByteArray(Charsets.ISO_8859_1)
checkHeader(byteArray)
// Mode
byteArray[7] shouldEqualTo 1
checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray)
compareArray(byteArray.copyOfRange(23 + 32, 23 + 64), tlx_byteArray)
compareArray(byteArray.copyOfRange(23 + 64, byteArray.size), sharedSecretByteArray)
}
@Test
fun testCase3() {
val url = qrCode3.toEncodedString()
val byteArray = url.toByteArray(Charsets.ISO_8859_1)
checkHeader(byteArray)
// Mode
byteArray[7] shouldEqualTo 2
checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), tlx_byteArray)
compareArray(byteArray.copyOfRange(23 + 32, 23 + 64), kte_byteArray)
compareArray(byteArray.copyOfRange(23 + 64, byteArray.size), sharedSecretByteArray)
}
@Test
fun testLongTransactionId() {
// Size on two bytes (2_000 = 0x07D0)
val longTransactionId = "PatternId_".repeat(200)
val qrCode = qrCode1.copy(transactionId = longTransactionId)
val result = qrCode.toEncodedString()
val expected = value1.replace("\u0000\u000DMaTransaction", "\u0007\u00D0$longTransactionId")
result shouldEqual expected
// Reverse operation
expected.toQrCodeData() shouldEqual qrCode
}
@Test
fun testAnyTransactionId() {
for (qty in 0 until 0x1FFF step 200) {
val longTransactionId = "a".repeat(qty)
val qrCode = qrCode1.copy(transactionId = longTransactionId)
// Symmetric operation
qrCode.toEncodedString().toQrCodeData() shouldEqual qrCode
}
}
// Error cases
@Test
fun testErrorHeader() {
value1.replace("MATRIX", "MOTRIX").toQrCodeData().shouldBeNull()
value1.replace("MATRIX", "MATRI").toQrCodeData().shouldBeNull()
value1.replace("MATRIX", "").toQrCodeData().shouldBeNull()
}
@Test
fun testErrorVersion() {
value1.replace("MATRIX\u0002", "MATRIX\u0000").toQrCodeData().shouldBeNull()
value1.replace("MATRIX\u0002", "MATRIX\u0001").toQrCodeData().shouldBeNull()
value1.replace("MATRIX\u0002", "MATRIX\u0003").toQrCodeData().shouldBeNull()
value1.replace("MATRIX\u0002", "MATRIX").toQrCodeData().shouldBeNull()
}
@Test
fun testErrorSecretTooShort() {
value1.replace("12345678", "1234567").toQrCodeData().shouldBeNull()
}
@Test
fun testErrorNoTransactionNoKeyNoSecret() {
// But keep transaction length
"MATRIX\u0002\u0000\u0000\u000D".toQrCodeData().shouldBeNull()
}
@Test
fun testErrorNoKeyNoSecret() {
"MATRIX\u0002\u0000\u0000\u000DMaTransaction".toQrCodeData().shouldBeNull()
}
@Test
fun testErrorTransactionLengthTooShort() {
// In this case, the secret will be longer, so this is not an error, but it will lead to keys mismatch
value1.replace("\u000DMaTransaction", "\u000CMaTransaction").toQrCodeData().shouldNotBeNull()
}
@Test
fun testErrorTransactionLengthTooBig() {
value1.replace("\u000DMaTransaction", "\u000EMaTransaction").toQrCodeData().shouldBeNull()
}
private fun compareArray(actual: ByteArray, expected: ByteArray) {
actual.size shouldEqual expected.size
for (i in actual.indices) {
actual[i] shouldEqualTo expected[i]
}
}
private fun checkHeader(byteArray: ByteArray) {
// MATRIX
byteArray[0] shouldEqualTo 'M'.toByte()
byteArray[1] shouldEqualTo 'A'.toByte()
byteArray[2] shouldEqualTo 'T'.toByte()
byteArray[3] shouldEqualTo 'R'.toByte()
byteArray[4] shouldEqualTo 'I'.toByte()
byteArray[5] shouldEqualTo 'X'.toByte()
// Version
byteArray[6] shouldEqualTo 2
}
private fun checkSizeAndTransaction(byteArray: ByteArray) {
// Size
byteArray[8] shouldEqualTo 0
byteArray[9] shouldEqualTo 13
// Transaction
byteArray.copyOfRange(10, 10 + "MaTransaction".length).toString(Charsets.ISO_8859_1) shouldEqual "MaTransaction"
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 New Vector Ltd * Copyright (c) 2020 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,14 +32,14 @@ class SharedSecretTest : InstrumentedTest {
@Test @Test
fun testSharedSecretLengthCase() { fun testSharedSecretLengthCase() {
repeat(100) { repeat(100) {
generateSharedSecret().length shouldBe 43 generateSharedSecretV2().length shouldBe 11
} }
} }
@Test @Test
fun testSharedDiffCase() { fun testSharedDiffCase() {
val sharedSecret1 = generateSharedSecret() val sharedSecret1 = generateSharedSecretV2()
val sharedSecret2 = generateSharedSecret() val sharedSecret2 = generateSharedSecretV2()
sharedSecret1 shouldNotBeEqualTo sharedSecret2 sharedSecret1 shouldNotBeEqualTo sharedSecret2
} }

View file

@ -18,8 +18,8 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.TestConstants import im.vector.matrix.android.common.TestConstants
@ -156,7 +156,7 @@ class VerificationTest : InstrumentedTest {
val bobSession = cryptoTestData.secondSession!! val bobSession = cryptoTestData.secondSession!!
mTestHelper.doSync<Unit> { callback -> mTestHelper.doSync<Unit> { callback ->
aliceSession.getCrossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(UserPasswordAuth( .initializeCrossSigning(UserPasswordAuth(
user = aliceSession.myUserId, user = aliceSession.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
@ -164,15 +164,15 @@ class VerificationTest : InstrumentedTest {
} }
mTestHelper.doSync<Unit> { callback -> mTestHelper.doSync<Unit> { callback ->
bobSession.getCrossSigningService() bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(UserPasswordAuth( .initializeCrossSigning(UserPasswordAuth(
user = bobSession.myUserId, user = bobSession.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
), callback) ), callback)
} }
val aliceVerificationService = aliceSession.getVerificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.getVerificationService() val bobVerificationService = bobSession.cryptoService().verificationService()
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
@ -227,6 +227,6 @@ class VerificationTest : InstrumentedTest {
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
} }
cryptoTestData.close() cryptoTestData.cleanUp(mTestHelper)
} }
} }

View file

@ -144,14 +144,6 @@ object MatrixPatterns {
* @return null if not found or if matrixId is null * @return null if not found or if matrixId is null
*/ */
fun extractServerNameFromId(matrixId: String?): String? { fun extractServerNameFromId(matrixId: String?): String? {
if (matrixId == null) { return matrixId?.substringAfter(":", missingDelimiterValue = "")?.takeIf { it.isNotEmpty() }
return null
}
val index = matrixId.indexOf(":")
return if (index == -1) {
null
} else matrixId.substring(index + 1)
} }
} }

View file

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

View file

@ -16,7 +16,7 @@
package im.vector.matrix.android.api.crypto package im.vector.matrix.android.api.crypto
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentation
import im.vector.matrix.android.internal.crypto.verification.getEmojiForCode import im.vector.matrix.android.internal.crypto.verification.getEmojiForCode
/** /**

View file

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

View file

@ -21,6 +21,7 @@ package im.vector.matrix.android.api.listeners
*/ */
interface ProgressListener { interface ProgressListener {
/** /**
* Will be invoked on the background thread, not in UI thread.
* @param progress from 0 to total by contract * @param progress from 0 to total by contract
* @param total * @param total
*/ */

View file

@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.failure.GlobalError import im.vector.matrix.android.api.failure.GlobalError
import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.cache.CacheService import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
@ -33,6 +34,7 @@ import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.securestorage.SecureStorageService import im.vector.matrix.android.api.session.securestorage.SecureStorageService
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
@ -47,7 +49,6 @@ interface Session :
RoomDirectoryService, RoomDirectoryService,
GroupService, GroupService,
UserService, UserService,
CryptoService,
CacheService, CacheService,
SignOutService, SignOutService,
FilterService, FilterService,
@ -57,7 +58,8 @@ interface Session :
PushersService, PushersService,
InitialSyncProgressService, InitialSyncProgressService,
HomeServerCapabilitiesService, HomeServerCapabilitiesService,
SecureStorageService { SecureStorageService,
AccountDataService {
/** /**
* The params associated to the session * The params associated to the session
@ -136,6 +138,11 @@ interface Session :
*/ */
fun contentUploadProgressTracker(): ContentUploadStateTracker fun contentUploadProgressTracker(): ContentUploadStateTracker
/**
* Returns the cryptoService associated with the session
*/
fun cryptoService(): CryptoService
/** /**
* Add a listener to the session. * Add a listener to the session.
* @param listener the listener to add. * @param listener the listener to add.
@ -159,4 +166,6 @@ interface Session :
*/ */
fun onGlobalError(globalError: GlobalError) fun onGlobalError(globalError: GlobalError)
} }
val sharedSecretStorageService: SharedSecretStorageService
} }

View file

@ -0,0 +1,53 @@
/*
* 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.accountdata
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
interface AccountDataService {
/**
* Retrieve the account data with the provided type or null if not found
*/
fun getAccountDataEvent(type: String): UserAccountDataEvent?
/**
* Observe the account data with the provided type
*/
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
/**
* Retrieve the account data with the provided types. The return list can have a different size that
* the size of the types set, because some AccountData may not exist.
* If an empty set is provided, all the AccountData are retrieved
*/
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent>
/**
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
*/
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>>
/**
* Update the account data with the provided type and the provided account data content
*/
fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>? = null): Cancelable
}

View file

@ -29,6 +29,7 @@ data class ContentAttachmentData(
val width: Long? = 0, val width: Long? = 0,
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
val name: String? = null, val name: String? = null,
val queryUri: String,
val path: String, val path: String,
val mimeType: String?, val mimeType: String?,
val type: Type val type: Type

View file

@ -23,7 +23,7 @@ import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.crypto.sas.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
@ -40,6 +40,12 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
interface CryptoService { interface CryptoService {
fun verificationService(): VerificationService
fun crossSigningService(): CrossSigningService
fun keysBackupService(): KeysBackupService
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>)
@ -50,12 +56,6 @@ interface CryptoService {
fun isCryptoEnabled(): Boolean fun isCryptoEnabled(): Boolean
fun getVerificationService(): VerificationService
fun getCrossSigningService(): CrossSigningService
fun getKeysBackupService(): KeysBackupService
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
fun setWarnOnUnknownDevices(warn: Boolean) fun setWarnOnUnknownDevices(warn: Boolean)

View file

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

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.crosssigning
const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master"
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"

View file

@ -14,8 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
// TODO Rename package package im.vector.matrix.android.api.session.crypto.verification
package im.vector.matrix.android.api.session.crypto.sas
enum class CancelCode(val value: String, val humanReadable: String) { enum class CancelCode(val value: String, val humanReadable: String) {
User("m.user", "the user cancelled the verification"), User("m.user", "the user cancelled the verification"),

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
import androidx.annotation.StringRes import androidx.annotation.StringRes

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
interface IncomingSasVerificationTransaction : SasVerificationTransaction { interface IncomingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState val uxState: UxState

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
interface OutgoingSasVerificationTransaction : SasVerificationTransaction { interface OutgoingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState val uxState: UxState

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
interface QrCodeVerificationTransaction : VerificationTransaction { interface QrCodeVerificationTransaction : VerificationTransaction {

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
object SasMode { object SasMode {
const val DECIMAL = "decimal" const val DECIMAL = "decimal"

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
interface SasVerificationTransaction : VerificationTransaction { interface SasVerificationTransaction : VerificationTransaction {

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
/** /**
* Verification methods * Verification methods

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.events.model.LocalEcho

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
interface VerificationTransaction { interface VerificationTransaction {

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.verification
sealed class VerificationTxState { sealed class VerificationTxState {
// Uninitialized state // Uninitialized state

View file

@ -66,6 +66,9 @@ object EventType {
const val ROOM_KEY_REQUEST = "m.room_key_request" const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
const val REQUEST_SECRET = "m.secret.request"
const val SEND_SECRET = "m.secret.send"
// Interactive key verification // Interactive key verification
const val KEY_VERIFICATION_START = "m.key.verification.start" const val KEY_VERIFICATION_START = "m.key.verification.start"
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept" const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"

View file

@ -20,7 +20,7 @@ import java.util.UUID
object LocalEcho { object LocalEcho {
private const val PREFIX = "local." private const val PREFIX = "\$local."
fun isLocalEchoId(eventId: String) = eventId.startsWith(PREFIX) fun isLocalEchoId(eventId: String) = eventId.startsWith(PREFIX)

View file

@ -35,9 +35,9 @@ interface RoomDirectoryService {
callback: MatrixCallback<PublicRoomsResponse>): Cancelable callback: MatrixCallback<PublicRoomsResponse>): Cancelable
/** /**
* Join a room by id * Join a room by id, or room alias
*/ */
fun joinRoom(roomId: String, fun joinRoom(roomIdOrAlias: String,
reason: String? = null, reason: String? = null,
callback: MatrixCallback<Unit>): Cancelable callback: MatrixCallback<Unit>): Cancelable

View file

@ -36,11 +36,11 @@ interface RoomService {
/** /**
* Join a room by id * Join a room by id
* @param roomId the roomId of the room to join * @param roomIdOrAlias the roomId or the room alias of the room to join
* @param reason optional reason for joining the room * @param reason optional reason for joining the room
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
*/ */
fun joinRoom(roomId: String, fun joinRoom(roomIdOrAlias: String,
reason: String? = null, reason: String? = null,
viaServers: List<String> = emptyList(), viaServers: List<String> = emptyList(),
callback: MatrixCallback<Unit>): Cancelable callback: MatrixCallback<Unit>): Cancelable

View file

@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.session.room.VerificationState
/** /**
* Contains an aggregated summary info of the references. * Contains an aggregated summary info of the references.
@ -26,6 +27,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class ReferencesAggregatedContent( data class ReferencesAggregatedContent(
// Verification status info for m.key.verification.request msgType events // Verification status info for m.key.verification.request msgType events
@Json(name = "verif_sum") val verificationSummary: String @Json(name = "verif_sum") val verificationState: VerificationState
// Add more fields for future summary info. // Add more fields for future summary info.
) )

View file

@ -45,7 +45,8 @@ data class RoomSummary constructor(
val versioningState: VersioningState = VersioningState.NONE, val versioningState: VersioningState = VersioningState.NONE,
val readMarkerId: String? = null, val readMarkerId: String? = null,
val userDrafts: List<UserDraft> = emptyList(), val userDrafts: List<UserDraft> = emptyList(),
var isEncrypted: Boolean, val isEncrypted: Boolean,
val inviterId: String? = null,
val typingRoomMemberIds: List<String> = emptyList(), val typingRoomMemberIds: List<String> = emptyList(),
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
// TODO Plug it // TODO Plug it

View file

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

View file

@ -21,5 +21,10 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class CreateRoomResponse( internal data class CreateRoomResponse(
@Json(name = "room_id") var roomId: String? = null /**
* Required. The created room's ID.
*/
@Json(name = "room_id") val roomId: String
) )
internal typealias JoinRoomResponse = CreateRoomResponse

View file

@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.verification.CancelCode
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent

View file

@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.verification.SasMode
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE

View file

@ -23,66 +23,71 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class PublicRoom( data class PublicRoom(
/** /**
* Aliases of the room. May be empty. * Aliases of the room. May be empty.
*/ */
@Json(name = "aliases") @Json(name = "aliases")
var aliases: List<String>? = null, val aliases: List<String>? = null,
/** /**
* The canonical alias of the room, if any. * The canonical alias of the room, if any.
*/ */
@Json(name = "canonical_alias") @Json(name = "canonical_alias")
var canonicalAlias: String? = null, val canonicalAlias: String? = null,
/** /**
* The name of the room, if any. * The name of the room, if any.
*/ */
@Json(name = "name") @Json(name = "name")
var name: String? = null, val name: String? = null,
/** /**
* Required. The number of members joined to the room. * Required. The number of members joined to the room.
*/ */
@Json(name = "num_joined_members") @Json(name = "num_joined_members")
var numJoinedMembers: Int = 0, val numJoinedMembers: Int = 0,
/** /**
* Required. The ID of the room. * Required. The ID of the room.
*/ */
@Json(name = "room_id") @Json(name = "room_id")
var roomId: String, val roomId: String,
/** /**
* The topic of the room, if any. * The topic of the room, if any.
*/ */
@Json(name = "topic") @Json(name = "topic")
var topic: String? = null, val topic: String? = null,
/** /**
* Required. Whether the room may be viewed by guest users without joining. * Required. Whether the room may be viewed by guest users without joining.
*/ */
@Json(name = "world_readable") @Json(name = "world_readable")
var worldReadable: Boolean = false, val worldReadable: Boolean = false,
/** /**
* Required. Whether guest users may join the room and participate in it. If they can, * Required. Whether guest users may join the room and participate in it. If they can,
* they will be subject to ordinary power level rules like any other user. * they will be subject to ordinary power level rules like any other user.
*/ */
@Json(name = "guest_can_join") @Json(name = "guest_can_join")
var guestCanJoin: Boolean = false, val guestCanJoin: Boolean = false,
/** /**
* The URL for the room's avatar, if one is set. * The URL for the room's avatar, if one is set.
*/ */
@Json(name = "avatar_url") @Json(name = "avatar_url")
var avatarUrl: String? = null, val avatarUrl: String? = null,
/** /**
* Undocumented item * Undocumented item
*/ */
@Json(name = "m.federate") @Json(name = "m.federate")
var isFederated: Boolean = false val isFederated: Boolean = false
) {
) /**
* Return the canonical alias, or the first alias from the list of aliases, or null
*/
fun getPrimaryAlias(): String? {
return canonicalAlias ?: aliases?.firstOrNull()
}
}

View file

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

View file

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

View file

@ -27,24 +27,24 @@ data class PublicRoomsResponse(
* A pagination token for the response. The absence of this token means there are no more results to fetch and the client should stop paginating. * A pagination token for the response. The absence of this token means there are no more results to fetch and the client should stop paginating.
*/ */
@Json(name = "next_batch") @Json(name = "next_batch")
var nextBatch: String? = null, val nextBatch: String? = null,
/** /**
* A pagination token that allows fetching previous results. The absence of this token means there are no results before this batch, * A pagination token that allows fetching previous results. The absence of this token means there are no results before this batch,
* i.e. this is the first batch. * i.e. this is the first batch.
*/ */
@Json(name = "prev_batch") @Json(name = "prev_batch")
var prevBatch: String? = null, val prevBatch: String? = null,
/** /**
* A paginated chunk of public rooms. * A paginated chunk of public rooms.
*/ */
@Json(name = "chunk") @Json(name = "chunk")
var chunk: List<PublicRoom>? = null, val chunk: List<PublicRoom>? = null,
/** /**
* An estimate on the total number of public rooms, if the server has an estimate. * An estimate on the total number of public rooms, if the server has an estimate.
*/ */
@Json(name = "total_room_count_estimate") @Json(name = "total_room_count_estimate")
var totalRoomCountEstimate: Int? = null val totalRoomCountEstimate: Int? = null
) )

View file

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

View file

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

View file

@ -23,6 +23,13 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomTombstoneContent( data class RoomTombstoneContent(
/**
* Required. A server-defined message.
*/
@Json(name = "body") val body: String? = null, @Json(name = "body") val body: String? = null,
@Json(name = "replacement_room") val replacementRoom: String?
/**
* Required. The new room the client should be visiting.
*/
@Json(name = "replacement_room") val replacementRoomId: String?
) )

View file

@ -51,16 +51,26 @@ interface SendService {
/** /**
* Method to send a media asynchronously. * Method to send a media asynchronously.
* @param attachment the media to send * @param attachment the media to send
* @param compressBeforeSending set to true to compress images before sending them
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @return a [Cancelable] * @return a [Cancelable]
*/ */
fun sendMedia(attachment: ContentAttachmentData): Cancelable fun sendMedia(attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable
/** /**
* Method to send a list of media asynchronously. * Method to send a list of media asynchronously.
* @param attachments the list of media to send * @param attachments the list of media to send
* @param compressBeforeSending set to true to compress images before sending them
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @return a [Cancelable] * @return a [Cancelable]
*/ */
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable fun sendMedias(attachments: List<ContentAttachmentData>,
compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable
/** /**
* Send a poll to the room. * Send a poll to the room.

View file

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

View file

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

View file

@ -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.api.session.securestorage
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataContent
/**
* The account_data will have an encrypted property that is a map from key ID to an object.
* The algorithm from the m.secret_storage.key.[key ID] data for the given key defines how the other properties are interpreted,
* though it's expected that most encryption schemes would have ciphertext and mac properties,
* where the ciphertext property is the unpadded base64-encoded ciphertext, and the mac is used to ensure the integrity of the data.
*/
@JsonClass(generateAdapter = true)
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 = "iv") val initializationVector: String? = null
) : AccountDataContent {
companion object {
/**
* Facility method to convert from object which must be comprised of maps, lists,
* strings, numbers, booleans and nulls.
*/
fun fromJson(obj: Any?): EncryptedSecretContent? {
return MoshiProvider.providesMoshi()
.adapter(EncryptedSecretContent::class.java)
.fromJsonValue(obj)
}
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.securestorage
sealed class IntegrityResult {
data class Success(val passphraseBased: Boolean) : IntegrityResult()
data class Error(val cause: SharedSecretStorageError) : IntegrityResult()
}

View file

@ -0,0 +1,24 @@
/*
* 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 KeyInfoResult {
data class Success(val keyInfo: KeyInfo) : KeyInfoResult()
data class Error(val error: SharedSecretStorageError) : KeyInfoResult()
fun isSuccess(): Boolean = this is Success
}

View file

@ -0,0 +1,21 @@
/*
* 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
interface KeySigner {
fun sign(canonicalJson: String): Map<String, Map<String, String>>?
}

View file

@ -0,0 +1,103 @@
/*
* 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
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.util.JsonCanonicalizer
/**
*
* The contents of the account data for the key will include an algorithm property, which indicates the encryption algorithm used, as well as a name property,
* which is a human-readable name.
* The contents will be signed as signed JSON using the user's master cross-signing key. Other properties depend on the encryption algorithm.
*
*
* "content": {
* "algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
* "passphrase": {
* "algorithm": "m.pbkdf2",
* "iterations": 500000,
* "salt": "IrswcMWnYieBALCAOMBw9k93xSzlc2su"
* },
* "pubkey": "qql1q3IvBbwMU97zLnyh9HYW5x/zqTy5eoK1n+9fm1Y",
* "signatures": {
* "@valere35:matrix.org": {
* "ed25519:nOUQYiH9L8uKp5JajqiQyv+Loa3+lsdil7UBverz/Ko": "QtePmwfUL7+SHYRJT/HaTgF7gUFog1E/wtUCt0qc5aB8N+Sz5iCOvQ0KtaFHQ5SJzsBlYH8k7ejoBc0RcnU7BA"
* }
* }
* }
*/
data class KeyInfo(
val id: String,
val content: SecretStorageKeyContent
)
@JsonClass(generateAdapter = true)
data class SecretStorageKeyContent(
/** Currently support m.secret_storage.v1.curve25519-aes-sha2 */
@Json(name = "algorithm") val algorithm: String? = null,
@Json(name = "name") val name: String? = null,
@Json(name = "passphrase") val passphrase: SsssPassphrase? = null,
@Json(name = "pubkey") val publicKey: String? = null,
@Json(name = "signatures") val signatures: Map<String, Map<String, String>>? = null
) {
private fun signalableJSONDictionary(): Map<String, Any> {
return mutableMapOf<String, Any>().apply {
algorithm
?.let { this["algorithm"] = it }
name
?.let { this["name"] = it }
publicKey
?.let { this["pubkey"] = it }
passphrase
?.let { ssssPassphrase ->
this["passphrase"] = mapOf(
"algorithm" to ssssPassphrase.algorithm,
"iterations" to ssssPassphrase.iterations,
"salt" to ssssPassphrase.salt
)
}
}
}
fun canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}
companion object {
/**
* Facility method to convert from object which must be comprised of maps, lists,
* strings, numbers, booleans and nulls.
*/
fun fromJson(obj: Any?): SecretStorageKeyContent? {
return MoshiProvider.providesMoshi()
.adapter(SecretStorageKeyContent::class.java)
.fromJsonValue(obj)
}
}
}
@JsonClass(generateAdapter = true)
data class SsssPassphrase(
@Json(name = "algorithm") val algorithm: String?,
@Json(name = "iterations") val iterations: Int,
@Json(name = "salt") val salt: String?
)

View file

@ -0,0 +1,32 @@
/*
* 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 SharedSecretStorageError(message: String?) : Throwable(message) {
data class UnknownSecret(val secretName: String) : SharedSecretStorageError("Unknown Secret $secretName")
data class UnknownKey(val keyId: String) : SharedSecretStorageError("Unknown key $keyId")
data class UnknownAlgorithm(val keyId: String) : SharedSecretStorageError("Unknown algorithm $keyId")
data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm")
data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName")
data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String)
: SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
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)
}

View file

@ -0,0 +1,118 @@
/*
* Copyright 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
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener
/**
* Some features may require clients to store encrypted data on the server so that it can be shared securely between clients.
* Clients may also wish to securely send such data directly to each other.
* For example, key backups (MSC1219) can store the decryption key for the backups on the server, or cross-signing (MSC1756) can store the signing keys.
*
* https://github.com/matrix-org/matrix-doc/pull/1946
*
*/
interface SharedSecretStorageService {
/**
* Generates a SSSS key for encrypting secrets.
* Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key ...)
*
* @param keyId the ID of the key
* @param keyName a human readable name
* @param keySigner Used to add a signature to the key (client should check key signature before storing secret)
*
* @param callback Get key creation info
*/
fun generateKey(keyId: String,
keyName: String,
keySigner: KeySigner?,
callback: MatrixCallback<SsssKeyCreationInfo>)
/**
* Generates a SSSS key using the given passphrase.
* Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key, salt, iteration ...)
*
* @param keyId the ID of the key
* @param keyName human readable key name
* @param passphrase The passphrase used to generate the key
* @param keySigner Used to add a signature to the key (client should check key signature before retrieving secret)
* @param progressListener The derivation of the passphrase may take long depending on the device, use this to report progress
*
* @param callback Get key creation info
*/
fun generateKeyWithPassphrase(keyId: String,
keyName: String,
passphrase: String,
keySigner: KeySigner,
progressListener: ProgressListener?,
callback: MatrixCallback<SsssKeyCreationInfo>)
fun getKey(keyId: String): KeyInfoResult
/**
* A key can be marked as the "default" key by setting the user's account_data with event type m.secret_storage.default_key
* to an object that has the ID of the key as its key property.
* The default key will be used to encrypt all secrets that the user would expect to be available on all their clients.
* Unless the user specifies otherwise, clients will try to use the default key to decrypt secrets.
*/
fun getDefaultKey(): KeyInfoResult
fun setDefaultKey(keyId: String, callback: MatrixCallback<Unit>)
/**
* Check whether we have a key with a given ID.
*
* @param keyId The ID of the key to check
* @return Whether we have the key.
*/
fun hasKey(keyId: String): Boolean
/**
* Store an encrypted secret on the server
* Clients MUST ensure that the key is trusted before using it to encrypt secrets.
*
* @param name The name of the secret
* @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<KeyRef>, callback: MatrixCallback<Unit>)
/**
* Use this call to determine which SSSSKeySpec to use for requesting secret
*/
fun getAlgorithmsForSecret(name: String): List<KeyInfoResult>
/**
* Get an encrypted secret from the shared storage
*
* @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 #RawBytesKeySpec)
*
*/
fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>)
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) : IntegrityResult
data class KeyRef(
val keyId: String?,
val keySpec: SsssKeySpec?
)
}

View file

@ -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.securestorage
data class SsssKeyCreationInfo(
val keyId: String = "",
var content: SecretStorageKeyContent?,
val recoveryKey: String = ""
)

View file

@ -0,0 +1,66 @@
/*
* 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
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.internal.crypto.keysbackup.deriveKey
import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
/** Tag class */
interface SsssKeySpec
data class RawBytesKeySpec(
val privateKey: ByteArray
) : SsssKeySpec {
companion object {
fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): RawBytesKeySpec {
return RawBytesKeySpec(
privateKey = deriveKey(
passphrase,
salt,
iterations,
progressListener
)
)
}
fun fromRecoveryKey(recoveryKey: String): RawBytesKeySpec? {
return extractCurveKeyFromRecoveryKey(recoveryKey)?.let {
RawBytesKeySpec(
privateKey = it
)
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RawBytesKeySpec
if (!privateKey.contentEquals(other.privateKey)) return false
return true
}
override fun hashCode(): Int {
return privateKey.contentHashCode()
}
}

View file

@ -24,4 +24,9 @@ data class User(
val userId: String, val userId: String,
val displayName: String? = null, val displayName: String? = null,
val avatarUrl: String? = null val avatarUrl: String? = null
) ) {
/**
* Return the display name or the user id
*/
fun getBestName() = displayName?.takeIf { it.isNotEmpty() } ?: userId
}

View file

@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import java.util.* import java.util.Locale
sealed class MatrixItem( sealed class MatrixItem(
open val id: String, open val id: String,
@ -143,8 +143,14 @@ sealed class MatrixItem(
* ========================================================================================== */ * ========================================================================================== */
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl) fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl) fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
// If no name is available, use room alias as Riot-Web does
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)

View file

@ -22,10 +22,19 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.RiotConfig import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
import im.vector.matrix.android.internal.auth.registration.* import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegistrationParams
import im.vector.matrix.android.internal.auth.registration.SuccessResult
import im.vector.matrix.android.internal.auth.registration.ValidationCodeBody
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call import retrofit2.Call
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Url
/** /**
* The login REST API. * The login REST API.

View file

@ -20,7 +20,13 @@ import android.net.Uri
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.auth.data.* import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure

View file

@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.ThreePidData import im.vector.matrix.android.internal.auth.registration.ThreePidData
import java.util.* import java.util.UUID
/** /**
* This class holds all pending data when creating a session, either by login or by register * This class holds all pending data when creating a session, either by login or by register

View file

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

View file

@ -31,6 +31,13 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
*/ */
const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2" const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
/**
* 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 // TODO Refacto: use this constants everywhere
const val ed25519 = "ed25519" const val ed25519 = "ed25519"
const val curve25519 = "curve25519" const val curve25519 = "curve25519"

View file

@ -49,7 +49,7 @@ import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryp
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
@ -122,7 +122,7 @@ internal class DefaultCryptoService @Inject constructor(
// Device list manager // Device list manager
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
// The key backup service. // The key backup service.
private val keysBackup: KeysBackup, private val keysBackupService: DefaultKeysBackupService,
// //
private val objectSigner: ObjectSigner, private val objectSigner: ObjectSigner,
// //
@ -301,7 +301,7 @@ internal class DefaultCryptoService @Inject constructor(
uploadDeviceKeys() uploadDeviceKeys()
oneTimeKeysUploader.maybeUploadOneTimeKeys() oneTimeKeysUploader.maybeUploadOneTimeKeys()
outgoingRoomKeyRequestManager.start() outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup() keysBackupService.checkAndStartKeysBackup()
if (isInitialSync) { if (isInitialSync) {
// refresh the devices list for each known room members // refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists() deviceListManager.invalidateAllDeviceLists()
@ -340,14 +340,14 @@ internal class DefaultCryptoService @Inject constructor(
/** /**
* @return the Keys backup Service * @return the Keys backup Service
*/ */
override fun getKeysBackupService() = keysBackup override fun keysBackupService() = keysBackupService
/** /**
* @return the VerificationService * @return the VerificationService
*/ */
override fun getVerificationService() = verificationService override fun verificationService() = verificationService
override fun getCrossSigningService() = crossSigningService override fun crossSigningService() = crossSigningService
/** /**
* A sync response has been received * A sync response has been received
@ -721,7 +721,7 @@ internal class DefaultCryptoService @Inject constructor(
Timber.e("## onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}") Timber.e("## onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
return return
} }
alg.onRoomKeyEvent(event, keysBackup) alg.onRoomKeyEvent(event, keysBackupService)
} }
/** /**
@ -887,7 +887,7 @@ internal class DefaultCryptoService @Inject constructor(
throw Exception("Error") throw Exception("Error")
} }
megolmSessionDataImporter.handle(importedSessions, true, uiHandler, progressListener) megolmSessionDataImporter.handle(importedSessions, true, progressListener)
} }
}.foldToCallback(callback) }.foldToCallback(callback)
} }
@ -1021,12 +1021,12 @@ internal class DefaultCryptoService @Inject constructor(
return return
} }
val requestBody = RoomKeyRequestBody() val requestBody = RoomKeyRequestBody(
algorithm = wireContent["algorithm"]?.toString(),
requestBody.roomId = event.roomId roomId = event.roomId,
requestBody.algorithm = wireContent["algorithm"]?.toString() senderKey = wireContent["sender_key"]?.toString(),
requestBody.senderKey = wireContent["sender_key"]?.toString() sessionId = wireContent["session_id"]?.toString()
requestBody.sessionId = wireContent["session_id"]?.toString() )
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
} }

View file

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

View file

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

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
interface IncomingRoomKeyRequestCommon {
/**
* The user id
*/
val userId: String?
/**
* The device id
*/
val deviceId: String?
/**
* The request id
*/
val requestId: String?
}

View file

@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
@ -51,11 +50,10 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
* @param event the announcement event. * @param event the announcement event.
*/ */
fun onRoomKeyRequestEvent(event: Event) { fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>() when (val roomKeyShareAction = event.getClearContent()?.get("action") as? String) {
when (roomKeyShare?.action) { RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) }
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event)) RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) }
RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event)) else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action $roomKeyShareAction")
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action ${roomKeyShare?.action}")
} }
} }

View file

@ -29,7 +29,12 @@ import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.convertFromUTF8 import im.vector.matrix.android.internal.util.convertFromUTF8
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import org.matrix.olm.* import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException
import org.matrix.olm.OlmMessage
import org.matrix.olm.OlmOutboundGroupSession
import org.matrix.olm.OlmSession
import org.matrix.olm.OlmUtility
import timber.log.Timber import timber.log.Timber
import java.net.URLEncoder import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject

View file

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

View file

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

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions
import android.os.Handler
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
@ -46,7 +45,6 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
@WorkerThread @WorkerThread
fun handle(megolmSessionsData: List<MegolmSessionData>, fun handle(megolmSessionsData: List<MegolmSessionData>,
fromBackup: Boolean, fromBackup: Boolean,
uiHandler: Handler,
progressListener: ProgressListener?): ImportRoomKeysResult { progressListener: ProgressListener?): ImportRoomKeysResult {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
@ -54,11 +52,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
var lastProgress = 0 var lastProgress = 0
var totalNumbersOfImportedKeys = 0 var totalNumbersOfImportedKeys = 0
if (progressListener != null) { progressListener?.onProgress(0, 100)
uiHandler.post {
progressListener.onProgress(0, 100)
}
}
val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData) val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData)
megolmSessionsData.forEachIndexed { cpt, megolmSessionData -> megolmSessionsData.forEachIndexed { cpt, megolmSessionData ->
@ -72,12 +66,12 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
totalNumbersOfImportedKeys++ totalNumbersOfImportedKeys++
// cancel any outstanding room key requests for this session // cancel any outstanding room key requests for this session
val roomKeyRequestBody = RoomKeyRequestBody() val roomKeyRequestBody = RoomKeyRequestBody(
algorithm = megolmSessionData.algorithm,
roomKeyRequestBody.algorithm = megolmSessionData.algorithm roomId = megolmSessionData.roomId,
roomKeyRequestBody.roomId = megolmSessionData.roomId senderKey = megolmSessionData.senderKey,
roomKeyRequestBody.senderKey = megolmSessionData.senderKey sessionId = megolmSessionData.sessionId
roomKeyRequestBody.sessionId = megolmSessionData.sessionId )
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody) outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody)
@ -89,8 +83,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
} }
if (progressListener != null) { if (progressListener != null) {
uiHandler.post { val progress = 100 * (cpt + 1) / totalNumbersOfKeys
val progress = 100 * cpt / totalNumbersOfKeys
if (lastProgress != progress) { if (lastProgress != progress) {
lastProgress = progress lastProgress = progress
@ -99,7 +92,6 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
} }
} }
} }
}
// Do not back up the key if it comes from a backup recovery // Do not back up the key if it comes from a backup recovery
if (fromBackup) { if (fromBackup) {

View file

@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber import timber.log.Timber
@ -26,7 +26,7 @@ import javax.inject.Inject
internal class SetDeviceVerificationAction @Inject constructor( internal class SetDeviceVerificationAction @Inject constructor(
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
@UserId private val userId: String, @UserId private val userId: String,
private val keysBackup: KeysBackup) { private val defaultKeysBackupService: DefaultKeysBackupService) {
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
val device = cryptoStore.getUserDevice(userId, deviceId) val device = cryptoStore.getUserDevice(userId, deviceId)
@ -42,7 +42,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
// If one of the user's own devices is being marked as verified / unverified, // If one of the user's own devices is being marked as verified / unverified,
// check the key backup status, since whether or not we use this depends on // check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device // whether it has a signature from a verified device
keysBackup.checkAndStartKeysBackup() defaultKeysBackupService.checkAndStartKeysBackup()
} }
} }

View file

@ -20,7 +20,7 @@ package im.vector.matrix.android.internal.crypto.algorithms
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
/** /**
* An interface for decrypting data * An interface for decrypting data
@ -41,7 +41,7 @@ internal interface IMXDecrypting {
* *
* @param event the key event. * @param event the key event.
*/ */
fun onRoomKeyEvent(event: Event, keysBackup: KeysBackup) {} fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
/** /**
* Check if the some messages can be decrypted with a new session * Check if the some messages can be decrypted with a new session

View file

@ -30,7 +30,7 @@ import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
@ -163,12 +163,12 @@ internal class MXMegolmDecryption(private val userId: String,
recipients.add(senderMap) recipients.add(senderMap)
} }
val requestBody = RoomKeyRequestBody() val requestBody = RoomKeyRequestBody(
roomId = event.roomId,
requestBody.roomId = event.roomId algorithm = encryptedEventContent.algorithm,
requestBody.algorithm = encryptedEventContent.algorithm senderKey = encryptedEventContent.senderKey,
requestBody.senderKey = encryptedEventContent.senderKey sessionId = encryptedEventContent.sessionId
requestBody.sessionId = encryptedEventContent.sessionId )
outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients) outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients)
} }
@ -198,7 +198,7 @@ internal class MXMegolmDecryption(private val userId: String,
* *
* @param event the key event. * @param event the key event.
*/ */
override fun onRoomKeyEvent(event: Event, keysBackup: KeysBackup) { override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
var exportFormat = false var exportFormat = false
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
@ -262,14 +262,14 @@ internal class MXMegolmDecryption(private val userId: String,
exportFormat) exportFormat)
if (added) { if (added) {
keysBackup.maybeBackupKeys() defaultKeysBackupService.maybeBackupKeys()
val content = RoomKeyRequestBody() val content = RoomKeyRequestBody(
algorithm = roomKeyContent.algorithm,
content.algorithm = roomKeyContent.algorithm roomId = roomKeyContent.roomId,
content.roomId = roomKeyContent.roomId sessionId = roomKeyContent.sessionId,
content.sessionId = roomKeyContent.sessionId senderKey = senderKey
content.senderKey = senderKey )
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content) outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content)
@ -290,8 +290,8 @@ internal class MXMegolmDecryption(private val userId: String,
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
val roomId = request.requestBody?.roomId ?: return false val roomId = request.requestBody?.roomId ?: return false
val senderKey = request.requestBody?.senderKey ?: return false val senderKey = request.requestBody.senderKey ?: return false
val sessionId = request.requestBody?.sessionId ?: return false val sessionId = request.requestBody.sessionId ?: return false
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId) return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
} }
@ -319,15 +319,14 @@ internal class MXMegolmDecryption(private val userId: String,
return@mapCatching return@mapCatching
} }
Timber.v("## shareKeysWithDevice() : sharing keys for session" + Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId") " ${body.senderKey}|${body.sessionId} with device $userId:$deviceId")
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) } runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) }
.fold( .fold(
{ {
// TODO // TODO
payloadJson["content"] = it.exportKeys() payloadJson["content"] = it.exportKeys() ?: ""
?: ""
}, },
{ {
// TODO // TODO

View file

@ -28,7 +28,7 @@ import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
@ -42,7 +42,7 @@ internal class MXMegolmEncryption(
// The id of the room we will be sending to. // The id of the room we will be sending to.
private var roomId: String, private var roomId: String,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val keysBackup: KeysBackup, private val defaultKeysBackupService: DefaultKeysBackupService,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
@ -85,7 +85,7 @@ internal class MXMegolmEncryption(
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
emptyList(), keysClaimedMap, false) emptyList(), keysClaimedMap, false)
keysBackup.maybeBackupKeys() defaultKeysBackupService.maybeBackupKeys()
return MXOutboundSessionInfo(sessionId) return MXOutboundSessionInfo(sessionId)
} }

View file

@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
@ -29,7 +29,7 @@ import javax.inject.Inject
internal class MXMegolmEncryptionFactory @Inject constructor( internal class MXMegolmEncryptionFactory @Inject constructor(
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val keysBackup: KeysBackup, private val defaultKeysBackupService: DefaultKeysBackupService,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
@ -42,7 +42,7 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
return MXMegolmEncryption( return MXMegolmEncryption(
roomId, roomId,
olmDevice, olmDevice,
keysBackup, defaultKeysBackupService,
cryptoStore, cryptoStore,
deviceListManager, deviceListManager,
ensureOlmSessionsForDevicesAction, ensureOlmSessionsForDevicesAction,

View file

@ -16,10 +16,29 @@
*/ */
package im.vector.matrix.android.internal.crypto.api package im.vector.matrix.android.internal.crypto.api
import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody
import im.vector.matrix.android.internal.crypto.model.rest.UploadSigningKeysBody
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call import retrofit2.Call
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.HTTP
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
internal interface CryptoApi { internal interface CryptoApi {

View file

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

View file

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

View file

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

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