mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-23 09:30:50 +03:00
Merge branch 'release/0.17.0'
This commit is contained in:
commit
128f3493b7
444 changed files with 9815 additions and 3781 deletions
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
33
CHANGES.md
33
CHANGES.md
|
@ -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)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,16 +41,15 @@ import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import java.util.Arrays
|
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
|
|
||||||
val messagesFromAlice: List<String> = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!")
|
private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
|
||||||
val messagesFromBob: List<String> = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
|
private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
|
||||||
|
|
||||||
val defaultSessionParams = SessionTestParams(true)
|
private val defaultSessionParams = SessionTestParams(true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return alice session
|
* @return alice session
|
||||||
|
@ -58,34 +57,23 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||||
|
|
||||||
var roomId: String? = null
|
val roomId = mTestHelper.doSync<String> {
|
||||||
val lock1 = CountDownLatch(1)
|
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
||||||
|
}
|
||||||
|
|
||||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) {
|
val room = aliceSession.getRoom(roomId)!!
|
||||||
override fun onSuccess(data: String) {
|
|
||||||
roomId = data
|
|
||||||
super.onSuccess(data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mTestHelper.await(lock1)
|
mTestHelper.doSync<Unit> {
|
||||||
assertNotNull(roomId)
|
room.enableEncryption(callback = it)
|
||||||
|
}
|
||||||
|
|
||||||
val room = aliceSession.getRoom(roomId!!)!!
|
return CryptoTestData(aliceSession, roomId)
|
||||||
|
|
||||||
val lock2 = CountDownLatch(1)
|
|
||||||
room.enableEncryption(callback = TestMatrixCallback(lock2))
|
|
||||||
mTestHelper.await(lock2)
|
|
||||||
|
|
||||||
return CryptoTestData(aliceSession, roomId!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return alice and bob sessions
|
* @return alice and bob sessions
|
||||||
*/
|
*/
|
||||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||||
val statuses = HashMap<String, String>()
|
|
||||||
|
|
||||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceRoomId = cryptoTestData.roomId
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
@ -94,7 +82,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
|
|
||||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||||
|
|
||||||
val lock1 = CountDownLatch(2)
|
val lock1 = CountDownLatch(1)
|
||||||
|
|
||||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||||
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||||
|
@ -103,7 +91,6 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||||
override fun onChanged(t: List<RoomSummary>?) {
|
override fun onChanged(t: List<RoomSummary>?) {
|
||||||
if (t?.isNotEmpty() == true) {
|
if (t?.isNotEmpty() == true) {
|
||||||
statuses["onNewRoom"] = "onNewRoom"
|
|
||||||
lock1.countDown()
|
lock1.countDown()
|
||||||
bobRoomSummariesLive.removeObserver(this)
|
bobRoomSummariesLive.removeObserver(this)
|
||||||
}
|
}
|
||||||
|
@ -114,26 +101,20 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
|
mTestHelper.doSync<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
aliceRoom.invite(bobSession.myUserId, callback = it)
|
||||||
statuses["invite"] = "invite"
|
}
|
||||||
super.onSuccess(data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mTestHelper.await(lock1)
|
mTestHelper.await(lock1)
|
||||||
|
|
||||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
val lock = CountDownLatch(1)
|
||||||
|
|
||||||
val lock2 = CountDownLatch(2)
|
|
||||||
|
|
||||||
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||||
override fun onChanged(t: List<RoomSummary>?) {
|
override fun onChanged(t: List<RoomSummary>?) {
|
||||||
if (bobSession.getRoom(aliceRoomId)
|
if (bobSession.getRoom(aliceRoomId)
|
||||||
?.getRoomMember(aliceSession.myUserId)
|
?.getRoomMember(aliceSession.myUserId)
|
||||||
?.membership == Membership.JOIN) {
|
?.membership == Membership.JOIN) {
|
||||||
statuses["AliceJoin"] = "AliceJoin"
|
lock.countDown()
|
||||||
lock2.countDown()
|
|
||||||
bobRoomSummariesLive.removeObserver(this)
|
bobRoomSummariesLive.removeObserver(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,19 +124,15 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
|
mTestHelper.doSync<Unit> { bobSession.joinRoom(aliceRoomId, callback = it) }
|
||||||
|
|
||||||
mTestHelper.await(lock2)
|
mTestHelper.await(lock)
|
||||||
|
|
||||||
// Ensure bob can send messages to the room
|
// Ensure bob can send messages to the room
|
||||||
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||||
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
||||||
|
|
||||||
assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin"))
|
|
||||||
|
|
||||||
// bobSession.dataHandler.removeListener(bobEventListener)
|
|
||||||
|
|
||||||
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
fail("Alice should see the new device")
|
||||||
fail("Alice should see the new device")
|
}
|
||||||
}
|
|
||||||
aliceKeysLatch.countDown()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mTestHelper.await(aliceKeysLatch)
|
|
||||||
|
|
||||||
val result = aliceSession.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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¤®õÁ\u008BA¥\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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,9 +122,9 @@ object MatrixPatterns {
|
||||||
*/
|
*/
|
||||||
fun isEventId(str: String?): Boolean {
|
fun isEventId(str: String?): Boolean {
|
||||||
return str != null
|
return str != null
|
||||||
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|
||||||
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|
||||||
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -42,6 +42,10 @@ interface CrossSigningService {
|
||||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||||
callback: MatrixCallback<Unit>? = null)
|
callback: MatrixCallback<Unit>? = null)
|
||||||
|
|
||||||
|
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||||
|
uskKeyPrivateKey: String?,
|
||||||
|
sskPrivateKey: String?) : UserTrustResult
|
||||||
|
|
||||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||||
|
|
||||||
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
||||||
|
@ -53,11 +57,13 @@ interface CrossSigningService {
|
||||||
fun trustUser(otherUserId: String,
|
fun trustUser(otherUserId: String,
|
||||||
callback: MatrixCallback<Unit>)
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun markMyMasterKeyAsTrusted()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign one of your devices and upload the signature
|
* Sign one of your devices and upload the signature
|
||||||
*/
|
*/
|
||||||
fun signDevice(deviceId: String,
|
fun trustDevice(deviceId: String,
|
||||||
callback: MatrixCallback<Unit>)
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun checkDeviceTrust(otherUserId: String,
|
fun checkDeviceTrust(otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String,
|
||||||
|
|
|
@ -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"
|
|
@ -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"),
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.api.session.room.model.create
|
package im.vector.matrix.android.api.session.room.model.create
|
||||||
|
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
|
import androidx.annotation.CheckResult
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.MatrixPatterns.isUserId
|
import im.vector.matrix.android.api.MatrixPatterns.isUserId
|
||||||
|
@ -120,37 +121,53 @@ data class CreateRoomParams(
|
||||||
@Json(name = "power_level_content_override")
|
@Json(name = "power_level_content_override")
|
||||||
val powerLevelContentOverride: PowerLevelsContent? = null
|
val powerLevelContentOverride: PowerLevelsContent? = null
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* Set to true means that if cross-signing is enabled and we can get keys for every invited users,
|
|
||||||
* the encryption will be enabled on the created room
|
|
||||||
*/
|
|
||||||
@Transient
|
@Transient
|
||||||
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
|
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams {
|
/**
|
||||||
enableEncryptionIfInvitedUsersSupportIt = true
|
* After calling this method, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
|
||||||
|
* the encryption will be enabled on the created room
|
||||||
|
* @param value true to activate this behavior.
|
||||||
|
* @return this, to allow chaining methods
|
||||||
|
*/
|
||||||
|
fun enableEncryptionIfInvitedUsersSupportIt(value: Boolean = true): CreateRoomParams {
|
||||||
|
enableEncryptionIfInvitedUsersSupportIt = value
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the crypto algorithm to the room creation parameters.
|
* Add the crypto algorithm to the room creation parameters.
|
||||||
*
|
*
|
||||||
* @param algorithm the algorithm
|
* @param enable true to enable encryption.
|
||||||
|
* @param algorithm the algorithm, default to [MXCRYPTO_ALGORITHM_MEGOLM], which is actually the only supported algorithm for the moment
|
||||||
|
* @return a modified copy of the CreateRoomParams object, or this if there is no modification
|
||||||
*/
|
*/
|
||||||
fun enableEncryptionWithAlgorithm(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
|
@CheckResult
|
||||||
|
fun enableEncryptionWithAlgorithm(enable: Boolean = true,
|
||||||
|
algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
|
||||||
|
// Remove the existing value if any.
|
||||||
|
val newInitialStates = initialStates
|
||||||
|
?.filter { it.type != EventType.STATE_ROOM_ENCRYPTION }
|
||||||
|
|
||||||
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
|
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||||
val contentMap = mapOf("algorithm" to algorithm)
|
if (enable) {
|
||||||
|
val contentMap = mapOf("algorithm" to algorithm)
|
||||||
|
|
||||||
val algoEvent = Event(
|
val algoEvent = Event(
|
||||||
type = EventType.STATE_ROOM_ENCRYPTION,
|
type = EventType.STATE_ROOM_ENCRYPTION,
|
||||||
stateKey = "",
|
stateKey = "",
|
||||||
content = contentMap.toContent()
|
content = contentMap.toContent()
|
||||||
)
|
)
|
||||||
|
|
||||||
copy(
|
copy(
|
||||||
initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent
|
initialStates = newInitialStates.orEmpty() + algoEvent
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
return copy(
|
||||||
|
initialStates = newInitialStates
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.e("Unsupported algorithm: $algorithm")
|
Timber.e("Unsupported algorithm: $algorithm")
|
||||||
this
|
this
|
||||||
|
@ -161,7 +178,9 @@ data class CreateRoomParams(
|
||||||
* Force the history visibility in the room creation parameters.
|
* Force the history visibility in the room creation parameters.
|
||||||
*
|
*
|
||||||
* @param historyVisibility the expected history visibility, set null to remove any existing value.
|
* @param historyVisibility the expected history visibility, set null to remove any existing value.
|
||||||
|
* @return a modified copy of the CreateRoomParams object
|
||||||
*/
|
*/
|
||||||
|
@CheckResult
|
||||||
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
|
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
|
||||||
// Remove the existing value if any.
|
// Remove the existing value if any.
|
||||||
val newInitialStates = initialStates
|
val newInitialStates = initialStates
|
||||||
|
@ -187,7 +206,9 @@ data class CreateRoomParams(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark as a direct message room.
|
* Mark as a direct message room.
|
||||||
|
* @return a modified copy of the CreateRoomParams object
|
||||||
*/
|
*/
|
||||||
|
@CheckResult
|
||||||
fun setDirectMessage(): CreateRoomParams {
|
fun setDirectMessage(): CreateRoomParams {
|
||||||
return copy(
|
return copy(
|
||||||
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
|
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
|
||||||
|
@ -195,20 +216,6 @@ data class CreateRoomParams(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the invite count
|
|
||||||
*/
|
|
||||||
private fun getInviteCount(): Int {
|
|
||||||
return invitedUserIds?.size ?: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the pid invite count
|
|
||||||
*/
|
|
||||||
private fun getInvite3PidCount(): Int {
|
|
||||||
return invite3pids?.size ?: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells if the created room can be a direct chat one.
|
* Tells if the created room can be a direct chat one.
|
||||||
*
|
*
|
||||||
|
@ -217,7 +224,6 @@ data class CreateRoomParams(
|
||||||
fun isDirect(): Boolean {
|
fun isDirect(): Boolean {
|
||||||
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
|
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
|
||||||
&& isDirect == true
|
&& isDirect == true
|
||||||
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,7 +238,9 @@ data class CreateRoomParams(
|
||||||
* ids might be a matrix id or an email address.
|
* ids might be a matrix id or an email address.
|
||||||
*
|
*
|
||||||
* @param ids the participant ids to add.
|
* @param ids the participant ids to add.
|
||||||
|
* @return a modified copy of the CreateRoomParams object
|
||||||
*/
|
*/
|
||||||
|
@CheckResult
|
||||||
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
||||||
userId: String,
|
userId: String,
|
||||||
ids: List<String>): CreateRoomParams {
|
ids: List<String>): CreateRoomParams {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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?
|
||||||
)
|
)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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>>?
|
||||||
|
}
|
|
@ -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?
|
||||||
|
)
|
|
@ -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)
|
||||||
|
}
|
|
@ -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?
|
||||||
|
)
|
||||||
|
}
|
|
@ -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 = ""
|
||||||
|
)
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,54 +25,56 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
|
||||||
/**
|
/**
|
||||||
* IncomingRoomKeyRequest class defines the incoming room keys request.
|
* IncomingRoomKeyRequest class defines the incoming room keys request.
|
||||||
*/
|
*/
|
||||||
open class IncomingRoomKeyRequest {
|
data class IncomingRoomKeyRequest(
|
||||||
/**
|
/**
|
||||||
* The user id
|
* The user id
|
||||||
*/
|
*/
|
||||||
var userId: String? = null
|
override val userId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The device id
|
* The device id
|
||||||
*/
|
*/
|
||||||
var deviceId: String? = null
|
override val deviceId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request id
|
* The request id
|
||||||
*/
|
*/
|
||||||
var requestId: String? = null
|
override val requestId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request body
|
* The request body
|
||||||
*/
|
*/
|
||||||
var requestBody: RoomKeyRequestBody? = null
|
val requestBody: RoomKeyRequestBody? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The runnable to call to accept to share the keys
|
* The runnable to call to accept to share the keys
|
||||||
*/
|
*/
|
||||||
@Transient
|
@Transient
|
||||||
var share: Runnable? = null
|
var share: Runnable? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The runnable to call to ignore the key share request.
|
* The runnable to call to ignore the key share request.
|
||||||
*/
|
*/
|
||||||
@Transient
|
@Transient
|
||||||
var ignore: Runnable? = null
|
var ignore: Runnable? = null
|
||||||
|
) : IncomingRoomKeyRequestCommon {
|
||||||
/**
|
companion object {
|
||||||
* Constructor
|
/**
|
||||||
*
|
* Factory
|
||||||
* @param event the event
|
*
|
||||||
*/
|
* @param event the event
|
||||||
constructor(event: Event) {
|
*/
|
||||||
userId = event.senderId
|
fun fromEvent(event: Event): IncomingRoomKeyRequest? {
|
||||||
val roomKeyShareRequest = event.getClearContent().toModel<RoomKeyShareRequest>()!!
|
return event.getClearContent()
|
||||||
deviceId = roomKeyShareRequest.requestingDeviceId
|
.toModel<RoomKeyShareRequest>()
|
||||||
requestId = roomKeyShareRequest.requestId
|
?.let {
|
||||||
requestBody = if (null != roomKeyShareRequest.body) roomKeyShareRequest.body else RoomKeyRequestBody()
|
IncomingRoomKeyRequest(
|
||||||
|
userId = event.senderId,
|
||||||
|
deviceId = it.requestingDeviceId,
|
||||||
|
requestId = it.requestId,
|
||||||
|
requestBody = it.body ?: RoomKeyRequestBody()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for object creation from crypto store
|
|
||||||
*/
|
|
||||||
constructor()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?
|
||||||
|
}
|
|
@ -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}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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,14 +83,12 @@ 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
|
||||||
|
|
||||||
progressListener.onProgress(progress, 100)
|
progressListener.onProgress(progress, 100)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -88,7 +88,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
Timber.i("## CrossSigning - Loading master key success")
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## CrossSigning - Public master key does not match the private key")
|
Timber.w("## CrossSigning - Public master key does not match the private key")
|
||||||
// TODO untrust
|
pkSigning.releaseSigning()
|
||||||
|
// TODO untrust?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
privateKeysInfo.user
|
privateKeysInfo.user
|
||||||
|
@ -100,7 +101,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
Timber.i("## CrossSigning - Loading User Signing key success")
|
Timber.i("## CrossSigning - Loading User Signing key success")
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## CrossSigning - Public User key does not match the private key")
|
Timber.w("## CrossSigning - Public User key does not match the private key")
|
||||||
// TODO untrust
|
pkSigning.releaseSigning()
|
||||||
|
// TODO untrust?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
privateKeysInfo.selfSigned
|
privateKeysInfo.selfSigned
|
||||||
|
@ -112,7 +114,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
Timber.i("## CrossSigning - Loading Self Signing key success")
|
Timber.i("## CrossSigning - Loading Self Signing key success")
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
|
Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
|
||||||
// TODO untrust
|
pkSigning.releaseSigning()
|
||||||
|
// TODO untrust?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,16 +227,18 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
val myDevice = myDeviceInfoHolder.get().myDevice
|
val myDevice = myDeviceInfoHolder.get().myDevice
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
|
||||||
val signedDevice = selfSigningPkOlm.sign(canonicalJson)
|
val signedDevice = selfSigningPkOlm.sign(canonicalJson)
|
||||||
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also {
|
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap())
|
||||||
it[userId] = (it[userId]
|
.also {
|
||||||
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
|
it[userId] = (it[userId]
|
||||||
}
|
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
|
||||||
|
}
|
||||||
myDevice.copy(signatures = updateSignatures).let {
|
myDevice.copy(signatures = updateSignatures).let {
|
||||||
uploadSignatureQueryBuilder.withDeviceInfo(it)
|
uploadSignatureQueryBuilder.withDeviceInfo(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign MSK with device key (migration) and upload signatures
|
// sign MSK with device key (migration) and upload signatures
|
||||||
olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign ->
|
val message = JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary())
|
||||||
|
olmDevice.signMessage(message)?.let { sign ->
|
||||||
val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap()
|
val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap()
|
||||||
?: HashMap()).also {
|
?: HashMap()).also {
|
||||||
it[userId] = (it[userId]
|
it[userId] = (it[userId]
|
||||||
|
@ -292,6 +297,80 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
cryptoStore.clearOtherUserTrust()
|
cryptoStore.clearOtherUserTrust()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||||
|
uskKeyPrivateKey: String?,
|
||||||
|
sskPrivateKey: String?
|
||||||
|
): UserTrustResult {
|
||||||
|
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
|
var masterKeyIsTrusted = false
|
||||||
|
var userKeyIsTrusted = false
|
||||||
|
var selfSignedKeyIsTrusted = false
|
||||||
|
|
||||||
|
masterKeyPrivateKey?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
try {
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
|
masterPkSigning?.releaseSigning()
|
||||||
|
masterPkSigning = pkSigning
|
||||||
|
masterKeyIsTrusted = true
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uskKeyPrivateKey?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
try {
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
|
userPkSigning?.releaseSigning()
|
||||||
|
userPkSigning = pkSigning
|
||||||
|
userKeyIsTrusted = true
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sskPrivateKey?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
try {
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
|
selfSigningPkSigning?.releaseSigning()
|
||||||
|
selfSigningPkSigning = pkSigning
|
||||||
|
selfSignedKeyIsTrusted = true
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
|
||||||
|
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
|
||||||
|
} else {
|
||||||
|
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||||
|
val checkSelfTrust = checkSelfTrust()
|
||||||
|
if (checkSelfTrust.isVerified()) {
|
||||||
|
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey)
|
||||||
|
setUserKeysAsTrusted(userId, true)
|
||||||
|
}
|
||||||
|
return checkSelfTrust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
||||||
|
@ -374,7 +453,9 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
?.fromBase64NoPadding()
|
?.fromBase64NoPadding()
|
||||||
|
|
||||||
var isMaterKeyTrusted = false
|
var isMaterKeyTrusted = false
|
||||||
if (masterPrivateKey != null) {
|
if (myMasterKey.trustLevel?.locallyVerified == true) {
|
||||||
|
isMaterKeyTrusted = true
|
||||||
|
} else if (masterPrivateKey != null) {
|
||||||
// Check if private match public
|
// Check if private match public
|
||||||
var olmPkSigning: OlmPkSigning? = null
|
var olmPkSigning: OlmPkSigning? = null
|
||||||
try {
|
try {
|
||||||
|
@ -507,7 +588,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}.executeBy(taskExecutor)
|
}.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun signDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
override fun markMyMasterKeyAsTrusted() {
|
||||||
|
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||||
|
checkSelfTrust()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||||
// This device should be yours
|
// This device should be yours
|
||||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
|
|
|
@ -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() {
|
||||||
eventBus.register(this)
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
BACKGROUND_HANDLER.post {
|
eventBus.register(this)
|
||||||
val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration)
|
BACKGROUND_HANDLER.post {
|
||||||
backgroundCryptoRealm.set(cryptoRealm)
|
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
||||||
// cryptoDevicesResult = cryptoRealm.where<DeviceInfoEntity>().findAll()
|
}
|
||||||
// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener)
|
|
||||||
|
|
||||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
eventBus.unregister(this)
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
BACKGROUND_HANDLER.post {
|
eventBus.unregister(this)
|
||||||
// cryptoDevicesResult?.removeAllChangeListeners()
|
BACKGROUND_HANDLER.post {
|
||||||
backgroundCryptoRealm.getAndSet(null).also {
|
backgroundSessionRealm.getAndSet(null).also {
|
||||||
it?.close()
|
it?.close()
|
||||||
}
|
}
|
||||||
backgroundSessionRealm.getAndSet(null).also {
|
|
||||||
it?.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
|
fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) {
|
||||||
taskExecutor.executorScope.launch {
|
if (!isStarted.get()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
|
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
|
||||||
// We need to send that back to session base
|
// We need to send that back to session base
|
||||||
|
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
backgroundSessionRealm.get()?.executeTransaction { realm ->
|
||||||
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
|
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,34 +90,47 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
|
fun onTrustUpdate(update: CryptoToSessionUserTrustChange) {
|
||||||
|
if (!isStarted.get()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
onCryptoDevicesChange(update.userIds)
|
onCryptoDevicesChange(update.userIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCryptoDevicesChange(users: List<String>) {
|
private fun onCryptoDevicesChange(users: List<String>) {
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java)
|
val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java)
|
||||||
.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
||||||
.findAll()
|
?.findAll()
|
||||||
.map { it.roomId }
|
?.map { it.roomId }
|
||||||
.distinct()
|
?.distinct()
|
||||||
|
|
||||||
val map = HashMap<String, List<String>>()
|
val map = HashMap<String, List<String>>()
|
||||||
impactedRoomsId.forEach { roomId ->
|
impactedRoomsId?.forEach { roomId ->
|
||||||
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId)
|
backgroundSessionRealm.get()?.let { realm ->
|
||||||
.findAll()
|
RoomMemberSummaryEntity.where(realm, roomId)
|
||||||
.let { results ->
|
.findAll()
|
||||||
map[roomId] = results.map { it.userId }
|
.let { results ->
|
||||||
}
|
map[roomId] = results.map { it.userId }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map.forEach { entry ->
|
map.forEach { entry ->
|
||||||
val roomId = entry.key
|
val roomId = entry.key
|
||||||
val userList = entry.value
|
val userList = entry.value
|
||||||
taskExecutor.executorScope.launch {
|
taskExecutor.executorScope.launch {
|
||||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
withContext(coroutineDispatchers.crypto) {
|
||||||
BACKGROUND_HANDLER.post {
|
try {
|
||||||
backgroundSessionRealm.get().executeTransaction { realm ->
|
// Can throw if the crypto database has been closed in between, in this case log and ignore?
|
||||||
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
||||||
|
BACKGROUND_HANDLER.post {
|
||||||
|
backgroundSessionRealm.get()?.executeTransaction { realm ->
|
||||||
|
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue