mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-28 14:08:44 +03:00
Merge develop into feature/stabilization_2
This commit is contained in:
commit
e6cd8a3a86
383 changed files with 17335 additions and 3848 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -15,3 +15,5 @@
|
||||||
ktlint
|
ktlint
|
||||||
.idea/copyright/New_vector.xml
|
.idea/copyright/New_vector.xml
|
||||||
.idea/copyright/profiles_settings.xml
|
.idea/copyright/profiles_settings.xml
|
||||||
|
|
||||||
|
.idea/copyright/New_Vector_Ltd.xml
|
||||||
|
|
47
CHANGES.md
47
CHANGES.md
|
@ -1,9 +1,43 @@
|
||||||
Changes in RiotX 0.14.0 (2020-XX-XX)
|
Changes in RiotX 0.15.0 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
-
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
-
|
||||||
|
|
||||||
|
Translations 🗣:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
-
|
||||||
|
|
||||||
|
Changes in RiotX 0.14.2 (2020-02-02)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Fix RiotX not starting issue
|
||||||
|
|
||||||
|
Changes in RiotX 0.14.1 (2020-02-02)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Cross-signing: fix UX issue when closing the bottom sheet verification (#813)
|
||||||
|
- Room and room member profile: fix issues on dark and black themes
|
||||||
|
|
||||||
|
Changes in RiotX 0.14.0 (2020-02-01)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- First implementation of Cross-signing
|
||||||
- Enable encryption in unencrypted rooms, from the room settings (#212)
|
- Enable encryption in unencrypted rooms, from the room settings (#212)
|
||||||
- Enable e2e by default when creating DM, and give the possibility to enable encryption when creating room (#837)
|
- Negotiate E2E by default for DMs (#907)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Sharing things to RiotX: sort list by recent room first (#771)
|
- Sharing things to RiotX: sort list by recent room first (#771)
|
||||||
|
@ -13,12 +47,6 @@ Improvements 🙌:
|
||||||
Other changes:
|
Other changes:
|
||||||
- Add support for /rainbow and /rainbowme commands (#879)
|
- Add support for /rainbow and /rainbowme commands (#879)
|
||||||
|
|
||||||
Bugfix 🐛:
|
|
||||||
-
|
|
||||||
|
|
||||||
Translations 🗣:
|
|
||||||
-
|
|
||||||
|
|
||||||
Build 🧱:
|
Build 🧱:
|
||||||
- Ensure builds are reproducible (#842)
|
- Ensure builds are reproducible (#842)
|
||||||
- F-Droid: fix the "-dev" issue in version name (#815)
|
- F-Droid: fix the "-dev" issue in version name (#815)
|
||||||
|
@ -120,6 +148,7 @@ Changes in RiotX 0.9.0 (2019-12-05)
|
||||||
Features ✨:
|
Features ✨:
|
||||||
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
||||||
- Iteration of the login flow (#613)
|
- Iteration of the login flow (#613)
|
||||||
|
- [SDK] MSC2241 / verification in DMs (#707)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Send mention Pills from composer
|
- Send mention Pills from composer
|
||||||
|
@ -325,7 +354,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
|
||||||
=======================================================
|
=======================================================
|
||||||
|
|
||||||
|
|
||||||
Changes in RiotX 0.0.0 (2020-XX-XX)
|
Changes in RiotX 0.X.0 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
|
|
@ -41,6 +41,9 @@ dependencies {
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
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.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||||
|
@ -28,23 +30,103 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
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.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 io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.functions.BiFunction
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room, private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||||
return room.getRoomSummaryLive().asObservable()
|
val summaryObservable = room.getRoomSummaryLive()
|
||||||
|
.asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
room.roomSummary().toOptional()
|
room.roomSummary().toOptional()
|
||||||
}
|
}
|
||||||
|
.doOnNext { Timber.v("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
|
||||||
|
|
||||||
|
val memberIdsChangeObservable = summaryObservable
|
||||||
|
.map {
|
||||||
|
it.getOrNull()?.let { roomSummary ->
|
||||||
|
if (roomSummary.isEncrypted) {
|
||||||
|
// Return the list of other users
|
||||||
|
roomSummary.otherMemberIds + listOf(session.myUserId)
|
||||||
|
} else {
|
||||||
|
// Return an empty list, the room is not encrypted
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}.orEmpty()
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.doOnNext { Timber.v("RX: memberIds emitted. Size: ${it.size}") }
|
||||||
|
|
||||||
|
// Observe the device info of the users in the room
|
||||||
|
val cryptoDeviceInfoObservable = memberIdsChangeObservable
|
||||||
|
.switchMap { membersIds ->
|
||||||
|
session.getLiveCryptoDeviceInfo(membersIds)
|
||||||
|
.asObservable()
|
||||||
|
.map {
|
||||||
|
// If any key change, emit the userIds list
|
||||||
|
membersIds
|
||||||
|
}
|
||||||
|
.startWith(membersIds)
|
||||||
|
.doOnNext { Timber.v("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||||
|
}
|
||||||
|
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
|
||||||
|
|
||||||
|
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
|
||||||
|
.map { userIds ->
|
||||||
|
if (userIds.isEmpty()) {
|
||||||
|
Optional<RoomEncryptionTrustLevel>(null)
|
||||||
|
} else {
|
||||||
|
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.doOnNext { Timber.v("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
|
||||||
|
|
||||||
|
return Observable
|
||||||
|
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
|
||||||
|
summaryObservable,
|
||||||
|
roomEncryptionTrustLevelObservable,
|
||||||
|
BiFunction { summary, level ->
|
||||||
|
summary.getOrNull()?.copy(
|
||||||
|
roomEncryptionTrustLevel = level.getOrNull()
|
||||||
|
).toOptional()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.doOnNext { Timber.v("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||||
return room.getRoomMembersLive(queryParams).asObservable()
|
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
room.getRoomMembers(queryParams)
|
room.getRoomMembers(queryParams)
|
||||||
}
|
}
|
||||||
|
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
|
||||||
|
|
||||||
|
// TODO Do it only for room members of the room (switchMap)
|
||||||
|
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||||
|
.startWith(emptyList<CryptoDeviceInfo>())
|
||||||
|
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||||
|
|
||||||
|
return Observable
|
||||||
|
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
|
||||||
|
roomMembersObservable,
|
||||||
|
cryptoDeviceInfoObservable,
|
||||||
|
BiFunction { summaries, _ ->
|
||||||
|
summaries.map {
|
||||||
|
if (room.isEncrypted()) {
|
||||||
|
it.copy(
|
||||||
|
// Get the trust level of a virtual room with only this user
|
||||||
|
userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.doOnNext { Timber.v("RX: final room members emitted. Size: ${it.size}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||||
|
@ -98,6 +180,6 @@ class RxRoom(private val room: Room) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(session: Session): RxRoom {
|
||||||
return RxRoom(this)
|
return RxRoom(this, session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.rx
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
|
@ -29,16 +30,43 @@ import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
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 io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.functions.BiFunction
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
session.getRoomSummaries(queryParams)
|
session.getRoomSummaries(queryParams)
|
||||||
}
|
}
|
||||||
|
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
|
||||||
|
|
||||||
|
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||||
|
.startWith(emptyList<CryptoDeviceInfo>())
|
||||||
|
.doOnNext { Timber.v("RX: crypto device info emitted: size: ${it.size}") }
|
||||||
|
|
||||||
|
return Observable
|
||||||
|
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
|
||||||
|
summariesObservable,
|
||||||
|
cryptoDeviceInfoObservable,
|
||||||
|
BiFunction { summaries, _ ->
|
||||||
|
summaries.map {
|
||||||
|
if (it.isEncrypted) {
|
||||||
|
it.copy(
|
||||||
|
roomEncryptionTrustLevel = session.getCrossSigningService()
|
||||||
|
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
|
@ -106,6 +134,15 @@ class RxSession(private val session: Session) {
|
||||||
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
|
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
|
||||||
session.getProfile(userId, it)
|
session.getProfile(userId, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
||||||
|
return session.getLiveCryptoDeviceInfo(userId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
||||||
|
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
||||||
|
.startWith(session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Session.rx(): RxSession {
|
fun Session.rx(): RxSession {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.test.core.app.ApplicationProvider
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface InstrumentedTest {
|
interface InstrumentedTest {
|
||||||
|
|
||||||
fun context(): Context {
|
fun context(): Context {
|
||||||
return ApplicationProvider.getApplicationContext()
|
return ApplicationProvider.getApplicationContext()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,8 @@ package im.vector.matrix.android
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
|
||||||
|
Executors.newSingleThreadExecutor().asCoroutineDispatcher())
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.common
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.MatrixConfiguration
|
import im.vector.matrix.android.api.MatrixConfiguration
|
||||||
|
@ -31,8 +32,16 @@ import im.vector.matrix.android.api.session.room.Room
|
||||||
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
|
||||||
import org.junit.Assert.*
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import java.util.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@ -73,23 +82,25 @@ class CommonTestHelper(context: Context) {
|
||||||
* @param session the session to sync
|
* @param session the session to sync
|
||||||
*/
|
*/
|
||||||
fun syncSession(session: Session) {
|
fun syncSession(session: Session) {
|
||||||
// val lock = CountDownLatch(1)
|
val lock = CountDownLatch(1)
|
||||||
|
|
||||||
// val observer = androidx.lifecycle.Observer<SyncState> { syncState ->
|
|
||||||
// if (syncState is SyncState.Idle) {
|
|
||||||
// lock.countDown()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO observe?
|
|
||||||
// while (session.syncState().value !is SyncState.Idle) {
|
|
||||||
// sleep(100)
|
|
||||||
// }
|
|
||||||
|
|
||||||
session.open()
|
session.open()
|
||||||
session.startSync(true)
|
session.startSync(true)
|
||||||
// await(lock)
|
|
||||||
// session.syncState().removeObserver(observer)
|
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||||
|
session.getSyncStateLive()
|
||||||
|
}
|
||||||
|
val syncObserver = object : Observer<SyncState> {
|
||||||
|
override fun onChanged(t: SyncState?) {
|
||||||
|
if (session.hasAlreadySynced()) {
|
||||||
|
lock.countDown()
|
||||||
|
syncLiveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
|
||||||
|
|
||||||
|
await(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -276,7 +287,7 @@ class CommonTestHelper(context: Context) {
|
||||||
|
|
||||||
fun signout(session: Session) {
|
fun signout(session: Session) {
|
||||||
val lock = CountDownLatch(1)
|
val lock = CountDownLatch(1)
|
||||||
session.signOut(true, object : TestMatrixCallback<Unit>(lock) {})
|
session.signOut(true, TestMatrixCallback(lock))
|
||||||
await(lock)
|
await(lock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,15 @@
|
||||||
package im.vector.matrix.android.common
|
package im.vector.matrix.android.common
|
||||||
|
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
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.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
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.toContent
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
|
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||||
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
|
||||||
|
@ -29,8 +33,16 @@ import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||||
import org.junit.Assert.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
import java.util.*
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import java.util.Arrays
|
||||||
|
import java.util.HashMap
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
|
@ -49,7 +61,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
var roomId: String? = null
|
var roomId: String? = null
|
||||||
val lock1 = CountDownLatch(1)
|
val lock1 = CountDownLatch(1)
|
||||||
|
|
||||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback<String>(lock1) {
|
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) {
|
||||||
override fun onSuccess(data: String) {
|
override fun onSuccess(data: String) {
|
||||||
roomId = data
|
roomId = data
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
|
@ -78,26 +90,31 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceRoomId = cryptoTestData.roomId
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
|
||||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
|
||||||
|
|
||||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||||
|
|
||||||
val lock1 = CountDownLatch(2)
|
val lock1 = CountDownLatch(2)
|
||||||
|
|
||||||
// val bobEventListener = object : MXEventListener() {
|
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||||
// override fun onNewRoom(roomId: String) {
|
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||||
// if (TextUtils.equals(roomId, aliceRoomId)) {
|
}
|
||||||
// if (!statuses.containsKey("onNewRoom")) {
|
|
||||||
// statuses["onNewRoom"] = "onNewRoom"
|
|
||||||
// lock1.countDown()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// bobSession.dataHandler.addListener(bobEventListener)
|
|
||||||
|
|
||||||
room.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
|
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||||
|
override fun onChanged(t: List<RoomSummary>?) {
|
||||||
|
if (t?.isNotEmpty() == true) {
|
||||||
|
statuses["onNewRoom"] = "onNewRoom"
|
||||||
|
lock1.countDown()
|
||||||
|
bobRoomSummariesLive.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
statuses["invite"] = "invite"
|
statuses["invite"] = "invite"
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
|
@ -108,25 +125,25 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||||
|
|
||||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
||||||
|
|
||||||
// bobSession.dataHandler.removeListener(bobEventListener)
|
|
||||||
|
|
||||||
val lock2 = CountDownLatch(2)
|
val lock2 = CountDownLatch(2)
|
||||||
|
|
||||||
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
|
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||||
|
override fun onChanged(t: List<RoomSummary>?) {
|
||||||
|
if (bobSession.getRoom(aliceRoomId)
|
||||||
|
?.getRoomMember(aliceSession.myUserId)
|
||||||
|
?.membership == Membership.JOIN) {
|
||||||
|
statuses["AliceJoin"] = "AliceJoin"
|
||||||
|
lock2.countDown()
|
||||||
|
bobRoomSummariesLive.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// room.addEventListener(object : MXEventListener() {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
// override fun onLiveEvent(event: Event, roomState: RoomState) {
|
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||||
// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) {
|
}
|
||||||
// val contentToConsider = event.contentAsJsonObject
|
|
||||||
// val member = JsonUtils.toRoomMember(contentToConsider)
|
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
|
||||||
//
|
|
||||||
// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) {
|
|
||||||
// statuses["AliceJoin"] = "AliceJoin"
|
|
||||||
// lock2.countDown()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
mTestHelper.await(lock2)
|
mTestHelper.await(lock2)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
|
import im.vector.matrix.android.common.SessionTestParams
|
||||||
|
import im.vector.matrix.android.common.TestConstants
|
||||||
|
import im.vector.matrix.android.common.TestMatrixCallback
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
class XSigningTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val mTestHelper = CommonTestHelper(context())
|
||||||
|
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_InitializeAndStoreKeys() {
|
||||||
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
|
val aliceLatch = CountDownLatch(1)
|
||||||
|
aliceSession.getCrossSigningService()
|
||||||
|
.initializeCrossSigning(UserPasswordAuth(
|
||||||
|
user = aliceSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
), TestMatrixCallback(aliceLatch))
|
||||||
|
|
||||||
|
mTestHelper.await(aliceLatch)
|
||||||
|
|
||||||
|
val myCrossSigningKeys = aliceSession.getCrossSigningService().getMyCrossSigningKeys()
|
||||||
|
val masterPubKey = myCrossSigningKeys?.masterKey()
|
||||||
|
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
||||||
|
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
||||||
|
assertNotNull("SelfSigned key should be stored", selfSigningKey?.unpaddedBase64PublicKey)
|
||||||
|
val userKey = myCrossSigningKeys?.userKey()
|
||||||
|
assertNotNull("User key should be stored", userKey?.unpaddedBase64PublicKey)
|
||||||
|
|
||||||
|
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
||||||
|
|
||||||
|
assertTrue("Signing Keys should be trusted", aliceSession.getCrossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
||||||
|
|
||||||
|
mTestHelper.signout(aliceSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_CrossSigningCheckBobSeesTheKeys() {
|
||||||
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
|
val aliceAuthParams = UserPasswordAuth(
|
||||||
|
user = aliceSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
)
|
||||||
|
val bobAuthParams = UserPasswordAuth(
|
||||||
|
user = bobSession!!.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
)
|
||||||
|
|
||||||
|
val latch = CountDownLatch(2)
|
||||||
|
|
||||||
|
aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
|
||||||
|
bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
|
||||||
|
|
||||||
|
mTestHelper.await(latch)
|
||||||
|
|
||||||
|
// Check that alice can see bob keys
|
||||||
|
val downloadLatch = CountDownLatch(1)
|
||||||
|
aliceSession.downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch))
|
||||||
|
mTestHelper.await(downloadLatch)
|
||||||
|
|
||||||
|
val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||||
|
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||||
|
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||||
|
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.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||||
|
|
||||||
|
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||||
|
|
||||||
|
mTestHelper.signout(aliceSession)
|
||||||
|
mTestHelper.signout(bobSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_CrossSigningTestAliceTrustBobNewDevice() {
|
||||||
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
|
val aliceAuthParams = UserPasswordAuth(
|
||||||
|
user = aliceSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
)
|
||||||
|
val bobAuthParams = UserPasswordAuth(
|
||||||
|
user = bobSession!!.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
)
|
||||||
|
|
||||||
|
val latch = CountDownLatch(2)
|
||||||
|
|
||||||
|
aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
|
||||||
|
bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
|
||||||
|
|
||||||
|
mTestHelper.await(latch)
|
||||||
|
|
||||||
|
// Check that alice can see bob keys
|
||||||
|
val downloadLatch = CountDownLatch(1)
|
||||||
|
val bobUserId = bobSession.myUserId
|
||||||
|
aliceSession.downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch))
|
||||||
|
mTestHelper.await(downloadLatch)
|
||||||
|
|
||||||
|
val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||||
|
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||||
|
|
||||||
|
val trustLatch = CountDownLatch(1)
|
||||||
|
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
|
||||||
|
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||||
|
|
||||||
|
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||||
|
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId
|
||||||
|
|
||||||
|
// Check that bob first session sees the new login
|
||||||
|
val bobKeysLatch = CountDownLatch(1)
|
||||||
|
bobSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
fail("Failed to get device")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||||
|
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
|
||||||
|
fail("Bob should see the new device")
|
||||||
|
}
|
||||||
|
bobKeysLatch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mTestHelper.await(bobKeysLatch)
|
||||||
|
|
||||||
|
val bobSecondDevicePOVFirstDevice = bobSession.getDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||||
|
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||||
|
|
||||||
|
// Manually mark it as trusted from first session
|
||||||
|
val bobSignLatch = CountDownLatch(1)
|
||||||
|
bobSession.getCrossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
bobSignLatch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
fail("Failed to trust bob ${failure.localizedMessage}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mTestHelper.await(bobSignLatch)
|
||||||
|
|
||||||
|
// Now alice should cross trust bob's second device
|
||||||
|
val aliceKeysLatch = CountDownLatch(1)
|
||||||
|
aliceSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
fail("Failed to get device")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||||
|
// check that the device is seen
|
||||||
|
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||||
|
fail("Alice should see the new device")
|
||||||
|
}
|
||||||
|
aliceKeysLatch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mTestHelper.await(aliceKeysLatch)
|
||||||
|
|
||||||
|
val result = aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||||
|
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||||
|
|
||||||
|
mTestHelper.signout(aliceSession)
|
||||||
|
mTestHelper.signout(bobSession)
|
||||||
|
mTestHelper.signout(bobSession2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,23 +25,36 @@ import im.vector.matrix.android.api.session.Session
|
||||||
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.keysbackup.KeysBackupState
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import im.vector.matrix.android.common.*
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
|
import im.vector.matrix.android.common.CryptoTestData
|
||||||
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
|
import im.vector.matrix.android.common.SessionTestParams
|
||||||
|
import im.vector.matrix.android.common.TestConstants
|
||||||
|
import im.vector.matrix.android.common.TestMatrixCallback
|
||||||
|
import im.vector.matrix.android.common.assertDictEquals
|
||||||
|
import im.vector.matrix.android.common.assertListEquals
|
||||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||||
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.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
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
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import java.util.*
|
import java.util.ArrayList
|
||||||
|
import java.util.Collections
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@ -298,7 +311,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||||
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey)
|
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey)
|
||||||
assertNotNull(decryption)
|
assertNotNull(decryption)
|
||||||
// - Check decryptKeyBackupData() returns stg
|
// - Check decryptKeyBackupData() returns stg
|
||||||
val sessionData = keysBackup.decryptKeyBackupData(keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!)
|
val sessionData = keysBackup
|
||||||
|
.decryptKeyBackupData(keyBackupData,
|
||||||
|
session.olmInboundGroupSession!!.sessionIdentifier(),
|
||||||
|
cryptoTestData.roomId,
|
||||||
|
decryption!!)
|
||||||
assertNotNull(sessionData)
|
assertNotNull(sessionData)
|
||||||
// - Compare the decrypted megolm key with the original one
|
// - Compare the decrypted megolm key with the original one
|
||||||
assertKeysEquals(session.exportKeys(), sessionData)
|
assertKeysEquals(session.exportKeys(), sessionData)
|
||||||
|
@ -1161,7 +1178,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||||
assertFalse(keysBackup2.isEnabled)
|
assertFalse(keysBackup2.isEnabled)
|
||||||
|
|
||||||
// - Validate the old device from the new one
|
// - Validate the old device from the new one
|
||||||
aliceSession2.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, oldDeviceId, aliceSession2.myUserId)
|
aliceSession2.setDeviceVerification(DeviceTrustLevel(false, true), aliceSession2.myUserId, oldDeviceId)
|
||||||
|
|
||||||
// -> Backup should automatically enable on the new device
|
// -> Backup should automatically enable on the new device
|
||||||
val latch4 = CountDownLatch(1)
|
val latch4 = CountDownLatch(1)
|
||||||
|
|
|
@ -19,22 +19,34 @@ 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.*
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.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
|
||||||
import im.vector.matrix.android.common.CryptoTestHelper
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
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.KeyVerificationAccept
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||||
import org.junit.Assert.*
|
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
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
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@ -50,53 +62,57 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val bobTxCreatedLatch = CountDownLatch(1)
|
val bobTxCreatedLatch = CountDownLatch(1)
|
||||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
val bobListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
bobTxCreatedLatch.countDown()
|
bobTxCreatedLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
bobSasMgr.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId)
|
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
|
||||||
|
bobSession.myUserId,
|
||||||
|
bobSession.getMyDevice().deviceId,
|
||||||
|
null)
|
||||||
assertNotNull("Alice should have a started transaction", txID)
|
assertNotNull("Alice should have a started transaction", txID)
|
||||||
|
|
||||||
val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!)
|
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
||||||
|
|
||||||
mTestHelper.await(bobTxCreatedLatch)
|
mTestHelper.await(bobTxCreatedLatch)
|
||||||
bobSasMgr.removeListener(bobListener)
|
bobVerificationService.removeListener(bobListener)
|
||||||
|
|
||||||
val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)
|
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||||
|
|
||||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||||
assertTrue(bobKeyTx is SASVerificationTransaction)
|
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
||||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||||
assertTrue(aliceKeyTx is SASVerificationTransaction)
|
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
||||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||||
|
|
||||||
val aliceSasTx = aliceKeyTx as SASVerificationTransaction?
|
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
||||||
val bobSasTx = bobKeyTx as SASVerificationTransaction?
|
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
||||||
|
|
||||||
assertEquals("Alice state should be started", SasVerificationTxState.Started, aliceSasTx!!.state)
|
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
||||||
assertEquals("Bob state should be started by alice", SasVerificationTxState.OnStarted, bobSasTx!!.state)
|
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
||||||
|
|
||||||
// Let's cancel from alice side
|
// Let's cancel from alice side
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
val bobListener2 = object : SasVerificationService.SasVerificationListener {
|
val bobListener2 = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.transactionId == txID) {
|
if (tx.transactionId == txID) {
|
||||||
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
|
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
||||||
|
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
||||||
cancelLatch.countDown()
|
cancelLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,29 +120,32 @@ class SASTest : InstrumentedTest {
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
bobSasMgr.addListener(bobListener2)
|
bobVerificationService.addListener(bobListener2)
|
||||||
|
|
||||||
aliceSasTx.cancel(CancelCode.User)
|
aliceSasTx.cancel(CancelCode.User)
|
||||||
mTestHelper.await(cancelLatch)
|
mTestHelper.await(cancelLatch)
|
||||||
|
|
||||||
assertEquals("Should be cancelled on alice side",
|
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
|
||||||
SasVerificationTxState.Cancelled, aliceSasTx.state)
|
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
|
||||||
assertEquals("Should be cancelled on bob side",
|
|
||||||
SasVerificationTxState.OnCancelled, bobSasTx.state)
|
|
||||||
|
|
||||||
assertEquals("Should be User cancelled on alice side",
|
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
|
||||||
CancelCode.User, aliceSasTx.cancelledReason)
|
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
|
||||||
assertEquals("Should be User cancelled on bob side",
|
|
||||||
CancelCode.User, aliceSasTx.cancelledReason)
|
|
||||||
|
|
||||||
assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID))
|
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
||||||
assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID))
|
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
||||||
|
|
||||||
|
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
||||||
|
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
||||||
|
|
||||||
|
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
||||||
|
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
||||||
|
|
||||||
cryptoTestData.close()
|
cryptoTestData.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_key_agreement_protocols_must_include_curve25519() {
|
fun test_key_agreement_protocols_must_include_curve25519() {
|
||||||
|
fail("Not passing for the moment")
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
@ -135,8 +154,23 @@ class SASTest : InstrumentedTest {
|
||||||
val tid = "00000000"
|
val tid = "00000000"
|
||||||
|
|
||||||
// Bob should receive a cancel
|
// Bob should receive a cancel
|
||||||
var canceledToDeviceEvent: Event? = null
|
var cancelReason: CancelCode? = null
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
|
val bobListener = object : VerificationService.VerificationListener {
|
||||||
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
||||||
|
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
||||||
|
cancelLatch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
|
}
|
||||||
|
bobSession.getVerificationService().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?) {
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||||
|
@ -152,31 +186,31 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceUserID = aliceSession.myUserId
|
val aliceUserID = aliceSession.myUserId
|
||||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||||
|
|
||||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
val aliceListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
(tx as IncomingSASVerificationTransaction).performAccept()
|
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
aliceSession.getSasVerificationService().addListener(aliceListener)
|
aliceSession.getVerificationService().addListener(aliceListener)
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
||||||
|
|
||||||
mTestHelper.await(cancelLatch)
|
mTestHelper.await(cancelLatch)
|
||||||
|
|
||||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
|
||||||
|
|
||||||
cryptoTestData.close()
|
cryptoTestData.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_key_agreement_macs_Must_include_hmac_sha256() {
|
fun test_key_agreement_macs_Must_include_hmac_sha256() {
|
||||||
|
fail("Not passing for the moment")
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
@ -214,6 +248,7 @@ class SASTest : InstrumentedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_key_agreement_short_code_include_decimal() {
|
fun test_key_agreement_short_code_include_decimal() {
|
||||||
|
fail("Not passing for the moment")
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
@ -253,18 +288,19 @@ class SASTest : InstrumentedTest {
|
||||||
aliceUserID: String?,
|
aliceUserID: String?,
|
||||||
aliceDevice: String?,
|
aliceDevice: String?,
|
||||||
tid: String,
|
tid: String,
|
||||||
protocols: List<String> = SASVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||||
hashes: List<String> = SASVerificationTransaction.KNOWN_HASHES,
|
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
||||||
mac: List<String> = SASVerificationTransaction.KNOWN_MACS,
|
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
||||||
codes: List<String> = SASVerificationTransaction.KNOWN_SHORT_CODES) {
|
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
|
||||||
val startMessage = KeyVerificationStart()
|
val startMessage = KeyVerificationStart(
|
||||||
startMessage.fromDevice = bobSession.getMyDevice().deviceId
|
fromDevice = bobSession.getMyDevice().deviceId,
|
||||||
startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS
|
method = VerificationMethod.SAS.toValue(),
|
||||||
startMessage.transactionID = tid
|
transactionID = tid,
|
||||||
startMessage.keyAgreementProtocols = protocols
|
keyAgreementProtocols = protocols,
|
||||||
startMessage.hashes = hashes
|
hashes = hashes,
|
||||||
startMessage.messageAuthenticationCodes = mac
|
messageAuthenticationCodes = mac,
|
||||||
startMessage.shortAuthenticationStrings = codes
|
shortAuthenticationStrings = codes
|
||||||
|
)
|
||||||
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
||||||
|
@ -287,31 +323,31 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
|
|
||||||
val aliceCreatedLatch = CountDownLatch(2)
|
val aliceCreatedLatch = CountDownLatch(2)
|
||||||
val aliceCancelledLatch = CountDownLatch(2)
|
val aliceCancelledLatch = CountDownLatch(2)
|
||||||
val createdTx = ArrayList<SASVerificationTransaction>()
|
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
||||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
val aliceListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
createdTx.add(tx as SASVerificationTransaction)
|
createdTx.add(tx as SASDefaultVerificationTransaction)
|
||||||
aliceCreatedLatch.countDown()
|
aliceCreatedLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
|
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
||||||
aliceCancelledLatch.countDown()
|
aliceCancelledLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
aliceSasMgr.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobUserId = bobSession!!.myUserId
|
val bobUserId = bobSession!!.myUserId
|
||||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||||
|
|
||||||
mTestHelper.await(aliceCreatedLatch)
|
mTestHelper.await(aliceCreatedLatch)
|
||||||
mTestHelper.await(aliceCancelledLatch)
|
mTestHelper.await(aliceCancelledLatch)
|
||||||
|
@ -329,46 +365,46 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
var accepted: KeyVerificationAccept? = null
|
var accepted: KeyVerificationAccept? = null
|
||||||
var startReq: KeyVerificationStart? = null
|
var startReq: KeyVerificationStart? = null
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
val aliceListener = object : VerificationService.VerificationListener {
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
|
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) {
|
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||||
val at = tx as SASVerificationTransaction
|
val at = tx as SASDefaultVerificationTransaction
|
||||||
accepted = at.accepted
|
accepted = at.accepted as? KeyVerificationAccept
|
||||||
startReq = at.startReq
|
startReq = at.startReq as? KeyVerificationStart
|
||||||
aliceAcceptedLatch.countDown()
|
aliceAcceptedLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aliceSasMgr.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
val bobListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
val at = tx as IncomingSASVerificationTransaction
|
val at = tx as IncomingSasVerificationTransaction
|
||||||
at.performAccept()
|
at.performAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
bobSasMgr.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||||
mTestHelper.await(aliceAcceptedLatch)
|
mTestHelper.await(aliceAcceptedLatch)
|
||||||
|
|
||||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||||
|
@ -393,38 +429,38 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
val aliceListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSASVerificationRequest).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||||
aliceSASLatch.countDown()
|
aliceSASLatch.countDown()
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
aliceSasMgr.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
val bobListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSASVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||||
tx.performAccept()
|
tx.performAccept()
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
||||||
bobSASLatch.countDown()
|
bobSASLatch.countDown()
|
||||||
|
@ -433,16 +469,16 @@ class SASTest : InstrumentedTest {
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
bobSasMgr.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||||
val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||||
mTestHelper.await(aliceSASLatch)
|
mTestHelper.await(aliceSASLatch)
|
||||||
mTestHelper.await(bobSASLatch)
|
mTestHelper.await(bobSASLatch)
|
||||||
|
|
||||||
val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASVerificationTransaction
|
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||||
val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASVerificationTransaction
|
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||||
|
|
||||||
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))
|
||||||
|
@ -457,36 +493,36 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
val aliceListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSASVerificationRequest).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||||
tx.userHasVerifiedShortCode()
|
tx.userHasVerifiedShortCode()
|
||||||
}
|
}
|
||||||
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
|
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||||
aliceSASLatch.countDown()
|
aliceSASLatch.countDown()
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
aliceSasMgr.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
val bobListener = object : VerificationService.VerificationListener {
|
||||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSASVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||||
tx.performAccept()
|
tx.performAccept()
|
||||||
|
@ -497,23 +533,23 @@ class SASTest : InstrumentedTest {
|
||||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||||
bobSASLatch.countDown()
|
bobSASLatch.countDown()
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
bobSasMgr.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
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: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
|
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
|
||||||
val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
|
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
|
||||||
|
|
||||||
// latch wait a bit again
|
// latch wait a bit again
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldNotBeEqualTo
|
||||||
|
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 SharedSecretTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedSecretLengthCase() {
|
||||||
|
repeat(100) {
|
||||||
|
generateSharedSecret().length shouldBe 43
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedDiffCase() {
|
||||||
|
val sharedSecret1 = generateSharedSecret()
|
||||||
|
val sharedSecret2 = generateSharedSecret()
|
||||||
|
|
||||||
|
sharedSecret1 shouldNotBeEqualTo sharedSecret2
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,10 @@
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application>
|
<application android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
<provider android:name="androidx.work.impl.WorkManagerInitializer"
|
<provider
|
||||||
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RoomEncryptionTrustLevel represents the trust level in an encrypted room.
|
||||||
|
*/
|
||||||
|
enum class RoomEncryptionTrustLevel {
|
||||||
|
// No one in the room has been verified -> Black shield
|
||||||
|
Default,
|
||||||
|
|
||||||
|
// There are one or more device un-verified -> the app should display a red shield
|
||||||
|
Warning,
|
||||||
|
|
||||||
|
// All devices in the room are verified -> the app should display a green shield
|
||||||
|
Trusted
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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.extensions
|
||||||
|
|
||||||
|
fun Boolean?.orTrue() = this ?: true
|
||||||
|
|
||||||
|
fun Boolean?.orFalse() = this ?: false
|
|
@ -17,14 +17,14 @@
|
||||||
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.api.comparators.DatedObjectComparators
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
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
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* MXDeviceInfo
|
* MXDeviceInfo
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||||
?.chunked(4)
|
?.chunked(4)
|
||||||
?.joinToString(separator = " ")
|
?.joinToString(separator = " ")
|
||||||
|
|
||||||
|
|
|
@ -26,3 +26,8 @@ fun Throwable.is401() =
|
||||||
fun Throwable.isTokenError() =
|
fun Throwable.isTokenError() =
|
||||||
this is Failure.ServerError
|
this is Failure.ServerError
|
||||||
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
|
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
|
||||||
|
|
||||||
|
fun Throwable.shouldBeRetried(): Boolean {
|
||||||
|
return this is Failure.NetworkConnection
|
||||||
|
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.permalinks
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful methods to create Matrix permalink.
|
* Useful methods to create Matrix permalink (matrix.to links).
|
||||||
*/
|
*/
|
||||||
object PermalinkFactory {
|
object PermalinkFactory {
|
||||||
|
|
||||||
|
@ -84,7 +84,17 @@ object PermalinkFactory {
|
||||||
* @param id the id to escape
|
* @param id the id to escape
|
||||||
* @return the escaped id
|
* @return the escaped id
|
||||||
*/
|
*/
|
||||||
private fun escape(id: String): String {
|
internal fun escape(id: String): String {
|
||||||
return id.replace("/", "%2F")
|
return id.replace("/", "%2F")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unescape '/' in id
|
||||||
|
*
|
||||||
|
* @param id the id to escape
|
||||||
|
* @return the escaped id
|
||||||
|
*/
|
||||||
|
internal fun unescape(id: String): String {
|
||||||
|
return id.replace("%2F", "/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,19 @@
|
||||||
package im.vector.matrix.android.api.session.crypto
|
package im.vector.matrix.android.api.session.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
|
import im.vector.matrix.android.api.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.SasVerificationService
|
import im.vector.matrix.android.api.session.crypto.sas.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
|
||||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
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
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
|
@ -46,7 +50,9 @@ interface CryptoService {
|
||||||
|
|
||||||
fun isCryptoEnabled(): Boolean
|
fun isCryptoEnabled(): Boolean
|
||||||
|
|
||||||
fun getSasVerificationService(): SasVerificationService
|
fun getVerificationService(): VerificationService
|
||||||
|
|
||||||
|
fun getCrossSigningService(): CrossSigningService
|
||||||
|
|
||||||
fun getKeysBackupService(): KeysBackupService
|
fun getKeysBackupService(): KeysBackupService
|
||||||
|
|
||||||
|
@ -54,15 +60,15 @@ interface CryptoService {
|
||||||
|
|
||||||
fun setWarnOnUnknownDevices(warn: Boolean)
|
fun setWarnOnUnknownDevices(warn: Boolean)
|
||||||
|
|
||||||
fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String)
|
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
|
||||||
|
|
||||||
fun getUserDevices(userId: String): MutableList<MXDeviceInfo>
|
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
||||||
|
|
||||||
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?)
|
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?)
|
||||||
|
|
||||||
fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo?
|
fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo?
|
||||||
|
|
||||||
fun getMyDevice(): MXDeviceInfo
|
fun getMyDevice(): CryptoDeviceInfo
|
||||||
|
|
||||||
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
||||||
|
|
||||||
|
@ -78,7 +84,7 @@ interface CryptoService {
|
||||||
|
|
||||||
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
||||||
|
|
||||||
fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo?
|
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||||
|
|
||||||
fun reRequestRoomKeyForEvent(event: Event)
|
fun reRequestRoomKeyForEvent(event: Event)
|
||||||
|
|
||||||
|
@ -110,7 +116,15 @@ interface CryptoService {
|
||||||
|
|
||||||
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
||||||
|
|
||||||
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
|
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
|
||||||
|
|
||||||
|
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto
|
package im.vector.matrix.android.api.session.crypto
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
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 org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ sealed class MXCryptoError : Throwable() {
|
||||||
|
|
||||||
data class OlmError(val olmException: OlmException) : MXCryptoError()
|
data class OlmError(val olmException: OlmException) : MXCryptoError()
|
||||||
|
|
||||||
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
|
data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError()
|
||||||
|
|
||||||
enum class ErrorType {
|
enum class ErrorType {
|
||||||
ENCRYPTING_NOT_ENABLED,
|
ENCRYPTING_NOT_ENABLED,
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.crosssigning
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
|
||||||
|
interface CrossSigningService {
|
||||||
|
|
||||||
|
fun isCrossSigningVerified(): Boolean
|
||||||
|
|
||||||
|
fun isUserTrusted(otherUserId: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will not force a download of the key, but will verify signatures trust chain.
|
||||||
|
* Checks that my trusted user key has signed the other user UserKey
|
||||||
|
*/
|
||||||
|
fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize cross signing for this user.
|
||||||
|
* Users needs to enter credentials
|
||||||
|
*/
|
||||||
|
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||||
|
callback: MatrixCallback<Unit>? = null)
|
||||||
|
|
||||||
|
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||||
|
|
||||||
|
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
||||||
|
|
||||||
|
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||||
|
|
||||||
|
fun canCrossSign(): Boolean
|
||||||
|
|
||||||
|
fun trustUser(otherUserId: String,
|
||||||
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign one of your devices and upload the signature
|
||||||
|
*/
|
||||||
|
fun signDevice(deviceId: String,
|
||||||
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun checkDeviceTrust(otherUserId: String,
|
||||||
|
otherDeviceId: String,
|
||||||
|
locallyTrusted: Boolean?): DeviceTrustResult
|
||||||
|
|
||||||
|
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.crosssigning
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.KeyUsage
|
||||||
|
|
||||||
|
data class MXCrossSigningInfo(
|
||||||
|
val userId: String,
|
||||||
|
val crossSigningKeys: List<CryptoCrossSigningKey>
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun isTrusted(): Boolean = masterKey()?.trustLevel?.isVerified() == true
|
||||||
|
&& selfSigningKey()?.trustLevel?.isVerified() == true
|
||||||
|
|
||||||
|
fun masterKey(): CryptoCrossSigningKey? = crossSigningKeys
|
||||||
|
.firstOrNull { it.usages?.contains(KeyUsage.MASTER.value) == true }
|
||||||
|
|
||||||
|
fun userKey(): CryptoCrossSigningKey? = crossSigningKeys
|
||||||
|
.firstOrNull { it.usages?.contains(KeyUsage.USER_SIGNING.value) == true }
|
||||||
|
|
||||||
|
fun selfSigningKey(): CryptoCrossSigningKey? = crossSigningKeys
|
||||||
|
.firstOrNull { it.usages?.contains(KeyUsage.SELF_SIGNING.value) == true }
|
||||||
|
}
|
|
@ -13,6 +13,8 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO Rename package
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
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) {
|
||||||
|
@ -25,7 +27,9 @@ enum class CancelCode(val value: String, val humanReadable: String) {
|
||||||
UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"),
|
UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"),
|
||||||
InvalidMessage("m.invalid_message", "an invalid message was received"),
|
InvalidMessage("m.invalid_message", "an invalid message was received"),
|
||||||
MismatchedKeys("m.key_mismatch", "Key mismatch"),
|
MismatchedKeys("m.key_mismatch", "Key mismatch"),
|
||||||
UserMismatchError("m.user_error", "User mismatch")
|
UserError("m.user_error", "User error"),
|
||||||
|
MismatchedUser("m.user_mismatch", "User mismatch"),
|
||||||
|
QrCodeInvalid("m.qr_code.invalid", "Invalid QR code")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun safeValueOf(code: String?): CancelCode {
|
fun safeValueOf(code: String?): CancelCode {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
package im.vector.matrix.android.api.session.crypto.sas
|
||||||
|
|
||||||
interface IncomingSasVerificationTransaction {
|
interface IncomingSasVerificationTransaction : SasVerificationTransaction {
|
||||||
val uxState: UxState
|
val uxState: UxState
|
||||||
|
|
||||||
fun performAccept()
|
fun performAccept()
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
package im.vector.matrix.android.api.session.crypto.sas
|
||||||
|
|
||||||
interface OutgoingSasVerificationRequest {
|
interface OutgoingSasVerificationTransaction : SasVerificationTransaction {
|
||||||
val uxState: UxState
|
val uxState: UxState
|
||||||
|
|
||||||
enum class UxState {
|
enum class UxState {
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.sas
|
||||||
|
|
||||||
|
interface QrCodeVerificationTransaction : VerificationTransaction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To use to display a qr code, for the other user to scan it
|
||||||
|
*/
|
||||||
|
val qrCodeText: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when you have scan the other user QR code
|
||||||
|
*/
|
||||||
|
fun userHasScannedOtherQrCode(otherQrCodeText: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when you confirm that other user has scanned your QR code
|
||||||
|
*/
|
||||||
|
fun otherUserScannedMyQrCode()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when you do not confirm that other user has scanned your QR code
|
||||||
|
*/
|
||||||
|
fun otherUserDidNotScannedMyQrCode()
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.sas
|
|
||||||
|
|
||||||
interface SasVerificationService {
|
|
||||||
fun addListener(listener: SasVerificationListener)
|
|
||||||
|
|
||||||
fun removeListener(listener: SasVerificationListener)
|
|
||||||
|
|
||||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
|
||||||
|
|
||||||
fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction?
|
|
||||||
|
|
||||||
fun beginKeyVerificationSAS(userId: String, deviceID: String): String?
|
|
||||||
|
|
||||||
fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
|
|
||||||
|
|
||||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
|
||||||
|
|
||||||
interface SasVerificationListener {
|
|
||||||
fun transactionCreated(tx: SasVerificationTransaction)
|
|
||||||
fun transactionUpdated(tx: SasVerificationTransaction)
|
|
||||||
fun markedAsManuallyVerified(userId: String, deviceId: String)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,18 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
package im.vector.matrix.android.api.session.crypto.sas
|
||||||
|
|
||||||
interface SasVerificationTransaction {
|
interface SasVerificationTransaction : VerificationTransaction {
|
||||||
val state: SasVerificationTxState
|
|
||||||
|
|
||||||
val cancelledReason: CancelCode?
|
|
||||||
|
|
||||||
val transactionId: String
|
|
||||||
|
|
||||||
val otherUserId: String
|
|
||||||
|
|
||||||
var otherDeviceId: String?
|
|
||||||
|
|
||||||
val isIncoming: Boolean
|
|
||||||
|
|
||||||
fun supportsEmoji(): Boolean
|
fun supportsEmoji(): Boolean
|
||||||
|
|
||||||
|
@ -37,14 +26,11 @@ interface SasVerificationTransaction {
|
||||||
|
|
||||||
fun getDecimalCodeRepresentation(): String
|
fun getDecimalCodeRepresentation(): String
|
||||||
|
|
||||||
/**
|
|
||||||
* User wants to cancel the transaction
|
|
||||||
*/
|
|
||||||
fun cancel()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To be called by the client when the user has verified that
|
* To be called by the client when the user has verified that
|
||||||
* both short codes do match
|
* both short codes do match
|
||||||
*/
|
*/
|
||||||
fun userHasVerifiedShortCode()
|
fun userHasVerifiedShortCode()
|
||||||
|
|
||||||
|
fun shortCodeDoesNotMatch()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.sas
|
|
||||||
|
|
||||||
enum class SasVerificationTxState {
|
|
||||||
None,
|
|
||||||
// I have started a verification request
|
|
||||||
SendingStart,
|
|
||||||
Started,
|
|
||||||
// Other user/device sent me a request
|
|
||||||
OnStarted,
|
|
||||||
// I have accepted a request started by the other user/device
|
|
||||||
SendingAccept,
|
|
||||||
Accepted,
|
|
||||||
// My request has been accepted by the other user/device
|
|
||||||
OnAccepted,
|
|
||||||
// I have sent my public key
|
|
||||||
SendingKey,
|
|
||||||
KeySent,
|
|
||||||
// The other user/device has sent me his public key
|
|
||||||
OnKeyReceived,
|
|
||||||
// Short code is ready to be displayed
|
|
||||||
ShortCodeReady,
|
|
||||||
// I have compared the code and manually said that they match
|
|
||||||
ShortCodeAccepted,
|
|
||||||
|
|
||||||
SendingMac,
|
|
||||||
MacSent,
|
|
||||||
Verifying,
|
|
||||||
Verified,
|
|
||||||
|
|
||||||
// Global: The verification has been cancelled (by me or other), see cancelReason for details
|
|
||||||
Cancelled,
|
|
||||||
OnCancelled
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.sas
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verification methods
|
||||||
|
*/
|
||||||
|
enum class VerificationMethod {
|
||||||
|
// Use it when your application supports the SAS verification method
|
||||||
|
SAS,
|
||||||
|
// Use it if your application is able to display QR codes
|
||||||
|
QR_CODE_SHOW,
|
||||||
|
// Use it if your application is able to scan QR codes
|
||||||
|
QR_CODE_SCAN
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.sas
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
||||||
|
*
|
||||||
|
* Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors.
|
||||||
|
* Verification is a user-friendly key verification process.
|
||||||
|
* Verification is intended to be a highly interactive process for users,
|
||||||
|
* and as such exposes verification methods which are easier for users to use.
|
||||||
|
*/
|
||||||
|
interface VerificationService {
|
||||||
|
|
||||||
|
fun addListener(listener: VerificationListener)
|
||||||
|
|
||||||
|
fun removeListener(listener: VerificationListener)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this device as verified manually
|
||||||
|
*/
|
||||||
|
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||||
|
|
||||||
|
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
|
||||||
|
|
||||||
|
fun getExistingVerificationRequest(otherUserId: String): List<PendingVerificationRequest>?
|
||||||
|
|
||||||
|
fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest?
|
||||||
|
|
||||||
|
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
|
||||||
|
|
||||||
|
fun beginKeyVerification(method: VerificationMethod,
|
||||||
|
otherUserId: String,
|
||||||
|
otherDeviceId: String,
|
||||||
|
transactionId: String?): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a key verification from another user using toDevice events.
|
||||||
|
*/
|
||||||
|
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
roomId: String,
|
||||||
|
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a key verification from another user using toDevice events.
|
||||||
|
*/
|
||||||
|
fun requestKeyVerification(methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
otherDevices: List<String>?): PendingVerificationRequest
|
||||||
|
|
||||||
|
fun declineVerificationRequestInDMs(otherUserId: String,
|
||||||
|
otherDeviceId: String,
|
||||||
|
transactionId: String,
|
||||||
|
roomId: String)
|
||||||
|
|
||||||
|
// Only SAS method is supported for the moment
|
||||||
|
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
||||||
|
transactionId: String,
|
||||||
|
roomId: String,
|
||||||
|
otherUserId: String,
|
||||||
|
otherDeviceId: String,
|
||||||
|
callback: MatrixCallback<String>?): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if the request is unknown
|
||||||
|
*/
|
||||||
|
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
roomId: String,
|
||||||
|
transactionId: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if the request is unknown
|
||||||
|
*/
|
||||||
|
fun readyPendingVerification(methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
transactionId: String): Boolean
|
||||||
|
|
||||||
|
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||||
|
|
||||||
|
interface VerificationListener {
|
||||||
|
fun transactionCreated(tx: VerificationTransaction)
|
||||||
|
fun transactionUpdated(tx: VerificationTransaction)
|
||||||
|
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
|
|
||||||
|
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
|
||||||
|
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000
|
||||||
|
private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
|
||||||
|
|
||||||
|
fun isValidRequest(age: Long?): Boolean {
|
||||||
|
if (age == null) return false
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val tooInThePast = now - TEN_MINUTES_IN_MILLIS
|
||||||
|
val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS
|
||||||
|
return age in tooInThePast..tooInTheFuture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.sas
|
||||||
|
|
||||||
|
interface VerificationTransaction {
|
||||||
|
|
||||||
|
var state: VerificationTxState
|
||||||
|
|
||||||
|
val transactionId: String
|
||||||
|
val otherUserId: String
|
||||||
|
var otherDeviceId: String?
|
||||||
|
|
||||||
|
// TODO Not used. Remove?
|
||||||
|
val isIncoming: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User wants to cancel the transaction
|
||||||
|
*/
|
||||||
|
fun cancel()
|
||||||
|
|
||||||
|
fun cancel(code: CancelCode)
|
||||||
|
|
||||||
|
fun isToDeviceTransport(): Boolean
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.sas
|
||||||
|
|
||||||
|
sealed class VerificationTxState {
|
||||||
|
// Uninitialized state
|
||||||
|
object None : VerificationTxState()
|
||||||
|
|
||||||
|
// Specific for SAS
|
||||||
|
abstract class VerificationSasTxState : VerificationTxState()
|
||||||
|
|
||||||
|
object SendingStart : VerificationSasTxState()
|
||||||
|
object Started : VerificationSasTxState()
|
||||||
|
object OnStarted : VerificationSasTxState()
|
||||||
|
object SendingAccept : VerificationSasTxState()
|
||||||
|
object Accepted : VerificationSasTxState()
|
||||||
|
object OnAccepted : VerificationSasTxState()
|
||||||
|
object SendingKey : VerificationSasTxState()
|
||||||
|
object KeySent : VerificationSasTxState()
|
||||||
|
object OnKeyReceived : VerificationSasTxState()
|
||||||
|
object ShortCodeReady : VerificationSasTxState()
|
||||||
|
object ShortCodeAccepted : VerificationSasTxState()
|
||||||
|
object SendingMac : VerificationSasTxState()
|
||||||
|
object MacSent : VerificationSasTxState()
|
||||||
|
object Verifying : VerificationSasTxState()
|
||||||
|
|
||||||
|
// Specific for QR code
|
||||||
|
abstract class VerificationQrTxState : VerificationTxState()
|
||||||
|
|
||||||
|
// Will be used to ask the user if the other user has correctly scanned
|
||||||
|
object QrScannedByOther : VerificationQrTxState()
|
||||||
|
|
||||||
|
// Terminal states
|
||||||
|
abstract class TerminalTxState : VerificationTxState()
|
||||||
|
|
||||||
|
object Verified : TerminalTxState()
|
||||||
|
|
||||||
|
// Cancelled by me or by other
|
||||||
|
data class Cancelled(val cancelCode: CancelCode, val byMe: Boolean) : TerminalTxState()
|
||||||
|
}
|
|
@ -85,6 +85,14 @@ data class Event(
|
||||||
@Transient
|
@Transient
|
||||||
var sendState: SendState = SendState.UNKNOWN
|
var sendState: SendState = SendState.UNKNOWN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `age` value transcoded in a timestamp based on the device clock when the SDK received
|
||||||
|
* the event from the home server.
|
||||||
|
* Unlike `age`, this value is static.
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
var ageLocalTs: Long? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if event is a state event.
|
* Check if event is a state event.
|
||||||
* @return true if event is state event.
|
* @return true if event is state event.
|
||||||
|
|
|
@ -72,6 +72,8 @@ object EventType {
|
||||||
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
||||||
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
||||||
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
||||||
|
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
|
||||||
|
const val KEY_VERIFICATION_READY = "m.key.verification.ready"
|
||||||
|
|
||||||
// Relation Events
|
// Relation Events
|
||||||
const val REACTION = "m.reaction"
|
const val REACTION = "m.reaction"
|
||||||
|
|
|
@ -21,9 +21,23 @@ import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class UnsignedData(
|
data class UnsignedData(
|
||||||
|
/**
|
||||||
|
* The time in milliseconds that has elapsed since the event was sent.
|
||||||
|
* This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers
|
||||||
|
* is out of sync, which can cause the age to either be negative or greater than it actually is.
|
||||||
|
*/
|
||||||
@Json(name = "age") val age: Long?,
|
@Json(name = "age") val age: Long?,
|
||||||
|
/**
|
||||||
|
* Optional. The event that redacted this event, if any.
|
||||||
|
*/
|
||||||
@Json(name = "redacted_because") val redactedEvent: Event? = null,
|
@Json(name = "redacted_because") val redactedEvent: Event? = null,
|
||||||
|
/**
|
||||||
|
* The client-supplied transaction ID, if the client being given the event is the same one which sent it.
|
||||||
|
*/
|
||||||
@Json(name = "transaction_id") val transactionId: String? = null,
|
@Json(name = "transaction_id") val transactionId: String? = null,
|
||||||
|
/**
|
||||||
|
* Optional. The previous content for this event. If there is no previous content, this key will be missing.
|
||||||
|
*/
|
||||||
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null,
|
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null,
|
||||||
@Json(name = "m.relations") val relations: AggregatedRelations? = null
|
@Json(name = "m.relations") val relations: AggregatedRelations? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,4 +101,6 @@ interface RoomService {
|
||||||
fun getRoomIdByAlias(roomAlias: String,
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
searchOnServer: Boolean,
|
searchOnServer: Boolean,
|
||||||
callback: MatrixCallback<Optional<String>>): Cancelable
|
callback: MatrixCallback<Optional<String>>): Cancelable
|
||||||
|
|
||||||
|
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,6 @@ package im.vector.matrix.android.api.session.room.model
|
||||||
data class EventAnnotationsSummary(
|
data class EventAnnotationsSummary(
|
||||||
var eventId: String,
|
var eventId: String,
|
||||||
var reactionsSummary: List<ReactionAggregatedSummary>,
|
var reactionsSummary: List<ReactionAggregatedSummary>,
|
||||||
var editSummary: EditAggregatedSummary?
|
var editSummary: EditAggregatedSummary?,
|
||||||
|
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains an aggregated summary info of the references.
|
||||||
|
* Put pre-computed info that you want to access quickly without having
|
||||||
|
* to go through all references events
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class ReferencesAggregatedContent(
|
||||||
|
// Verification status info for m.key.verification.request msgType events
|
||||||
|
@Json(name = "verif_sum") val verificationSummary: String
|
||||||
|
// Add more fields for future summary info.
|
||||||
|
)
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events can relates to other events, this object keeps a summary
|
||||||
|
* of all events that are referencing the 'eventId' event via the RelationType.REFERENCE
|
||||||
|
*/
|
||||||
|
data class ReferencesAggregatedSummary(
|
||||||
|
val eventId: String,
|
||||||
|
val content: Content?,
|
||||||
|
val sourceEvents: List<String>,
|
||||||
|
val localEchos: List<String>
|
||||||
|
)
|
|
@ -16,12 +16,16 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
||||||
*/
|
*/
|
||||||
data class RoomMemberSummary(
|
data class RoomMemberSummary constructor(
|
||||||
val membership: Membership,
|
val membership: Membership,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
val avatarUrl: String? = null
|
val avatarUrl: String? = null,
|
||||||
|
// TODO Warning: Will not be populated if not using RxRoom
|
||||||
|
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
@ -24,7 +25,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
* This class holds some data of a room.
|
* This class holds some data of a room.
|
||||||
* It can be retrieved by [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
|
* It can be retrieved by [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
|
||||||
*/
|
*/
|
||||||
data class RoomSummary(
|
data class RoomSummary constructor(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val displayName: String = "",
|
val displayName: String = "",
|
||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
|
@ -46,7 +47,9 @@ data class RoomSummary(
|
||||||
val userDrafts: List<UserDraft> = emptyList(),
|
val userDrafts: List<UserDraft> = emptyList(),
|
||||||
var isEncrypted: Boolean,
|
var isEncrypted: Boolean,
|
||||||
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
|
||||||
|
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isVersioned: Boolean
|
val isVersioned: Boolean
|
||||||
|
|
|
@ -35,95 +35,111 @@ import timber.log.Timber
|
||||||
* Parameter to create a room, with facilities functions to configure it
|
* Parameter to create a room, with facilities functions to configure it
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
class CreateRoomParams {
|
data class CreateRoomParams(
|
||||||
|
/**
|
||||||
|
* A public visibility indicates that the room will be shown in the published room list.
|
||||||
|
* A private visibility will hide the room from the published room list.
|
||||||
|
* Rooms default to private visibility if this key is not included.
|
||||||
|
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
|
||||||
|
*/
|
||||||
|
@Json(name = "visibility")
|
||||||
|
val visibility: RoomDirectoryVisibility? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A public visibility indicates that the room will be shown in the published room list.
|
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
|
||||||
* A private visibility will hide the room from the published room list.
|
* The alias will belong on the same homeserver which created the room.
|
||||||
* Rooms default to private visibility if this key is not included.
|
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
|
||||||
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
|
*/
|
||||||
*/
|
@Json(name = "room_alias_name")
|
||||||
var visibility: RoomDirectoryVisibility? = null
|
val roomAliasName: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
|
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
|
||||||
* The alias will belong on the same homeserver which created the room.
|
* See Room Events for more information on m.room.name.
|
||||||
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
|
*/
|
||||||
*/
|
@Json(name = "name")
|
||||||
@Json(name = "room_alias_name")
|
val name: String? = null,
|
||||||
var roomAliasName: String? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
|
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
|
||||||
* See Room Events for more information on m.room.name.
|
* See Room Events for more information on m.room.topic.
|
||||||
*/
|
*/
|
||||||
var name: String? = null
|
@Json(name = "topic")
|
||||||
|
val topic: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
|
* A list of user IDs to invite to the room.
|
||||||
* See Room Events for more information on m.room.topic.
|
* This will tell the server to invite everyone in the list to the newly created room.
|
||||||
*/
|
*/
|
||||||
var topic: String? = null
|
@Json(name = "invite")
|
||||||
|
val invitedUserIds: List<String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of user IDs to invite to the room.
|
* A list of objects representing third party IDs to invite into the room.
|
||||||
* This will tell the server to invite everyone in the list to the newly created room.
|
*/
|
||||||
*/
|
@Json(name = "invite_3pid")
|
||||||
@Json(name = "invite")
|
val invite3pids: List<Invite3Pid>? = null,
|
||||||
var invitedUserIds: MutableList<String>? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of objects representing third party IDs to invite into the room.
|
* Extra keys to be added to the content of the m.room.create.
|
||||||
*/
|
* The server will clobber the following keys: creator.
|
||||||
@Json(name = "invite_3pid")
|
* Future versions of the specification may allow the server to clobber other keys.
|
||||||
var invite3pids: MutableList<Invite3Pid>? = null
|
*/
|
||||||
|
@Json(name = "creation_content")
|
||||||
|
val creationContent: Any? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extra keys to be added to the content of the m.room.create.
|
* A list of state events to set in the new room.
|
||||||
* The server will clobber the following keys: creator.
|
* This allows the user to override the default state events set in the new room.
|
||||||
* Future versions of the specification may allow the server to clobber other keys.
|
* The expected format of the state events are an object with type, state_key and content keys set.
|
||||||
*/
|
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
|
||||||
@Json(name = "creation_content")
|
*/
|
||||||
var creationContent: Any? = null
|
@Json(name = "initial_state")
|
||||||
|
val initialStates: List<Event>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of state events to set in the new room.
|
* Convenience parameter for setting various default state events based on a preset. Must be either:
|
||||||
* This allows the user to override the default state events set in the new room.
|
* private_chat => join_rules is set to invite. history_visibility is set to shared.
|
||||||
* The expected format of the state events are an object with type, state_key and content keys set.
|
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
|
||||||
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
|
* room creator.
|
||||||
*/
|
* public_chat: => join_rules is set to public. history_visibility is set to shared.
|
||||||
@Json(name = "initial_state")
|
*/
|
||||||
var initialStates: MutableList<Event>? = null
|
@Json(name = "preset")
|
||||||
|
val preset: CreateRoomPreset? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience parameter for setting various default state events based on a preset. Must be either:
|
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
|
||||||
* private_chat => join_rules is set to invite. history_visibility is set to shared.
|
* See Direct Messaging for more information.
|
||||||
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
|
*/
|
||||||
* room creator.
|
@Json(name = "is_direct")
|
||||||
* public_chat: => join_rules is set to public. history_visibility is set to shared.
|
val isDirect: Boolean? = null,
|
||||||
*/
|
|
||||||
var preset: CreateRoomPreset? = null
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The power level content to override in the default power level event
|
||||||
|
*/
|
||||||
|
@Json(name = "power_level_content_override")
|
||||||
|
val powerLevelContentOverride: PowerLevelsContent? = null
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
|
* Set to true means that if cross-signing is enabled and we can get keys for every invited users,
|
||||||
* See Direct Messaging for more information.
|
* the encryption will be enabled on the created room
|
||||||
*/
|
*/
|
||||||
@Json(name = "is_direct")
|
@Transient
|
||||||
var isDirect: Boolean? = null
|
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
/**
|
fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams {
|
||||||
* The power level content to override in the default power level event
|
enableEncryptionIfInvitedUsersSupportIt = true
|
||||||
*/
|
return this
|
||||||
@Json(name = "power_level_content_override")
|
}
|
||||||
var powerLevelContentOverride: PowerLevelsContent? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the crypto algorithm to the room creation parameters.
|
* Add the crypto algorithm to the room creation parameters.
|
||||||
*
|
*
|
||||||
* @param algorithm the algorithm
|
* @param algorithm the algorithm
|
||||||
*/
|
*/
|
||||||
fun enableEncryptionWithAlgorithm(algorithm: String) {
|
fun enableEncryptionWithAlgorithm(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
|
||||||
if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
|
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||||
val contentMap = mapOf("algorithm" to algorithm)
|
val contentMap = mapOf("algorithm" to algorithm)
|
||||||
|
|
||||||
val algoEvent = Event(
|
val algoEvent = Event(
|
||||||
|
@ -132,13 +148,12 @@ class CreateRoomParams {
|
||||||
content = contentMap.toContent()
|
content = contentMap.toContent()
|
||||||
)
|
)
|
||||||
|
|
||||||
if (null == initialStates) {
|
copy(
|
||||||
initialStates = mutableListOf(algoEvent)
|
initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent
|
||||||
} else {
|
)
|
||||||
initialStates!!.add(algoEvent)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Timber.e("Unsupported algorithm: $algorithm")
|
Timber.e("Unsupported algorithm: $algorithm")
|
||||||
|
this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,9 +162,10 @@ class CreateRoomParams {
|
||||||
*
|
*
|
||||||
* @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.
|
||||||
*/
|
*/
|
||||||
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
|
||||||
// Remove the existing value if any.
|
// Remove the existing value if any.
|
||||||
initialStates?.removeAll { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }
|
val newInitialStates = initialStates
|
||||||
|
?.filter { it.type != EventType.STATE_ROOM_HISTORY_VISIBILITY }
|
||||||
|
|
||||||
if (historyVisibility != null) {
|
if (historyVisibility != null) {
|
||||||
val contentMap = mapOf("history_visibility" to historyVisibility)
|
val contentMap = mapOf("history_visibility" to historyVisibility)
|
||||||
|
@ -159,20 +175,24 @@ class CreateRoomParams {
|
||||||
stateKey = "",
|
stateKey = "",
|
||||||
content = contentMap.toContent())
|
content = contentMap.toContent())
|
||||||
|
|
||||||
if (null == initialStates) {
|
return copy(
|
||||||
initialStates = mutableListOf(historyVisibilityEvent)
|
initialStates = newInitialStates.orEmpty() + historyVisibilityEvent
|
||||||
} else {
|
)
|
||||||
initialStates!!.add(historyVisibilityEvent)
|
} else {
|
||||||
}
|
return copy(
|
||||||
|
initialStates = newInitialStates
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark as a direct message room.
|
* Mark as a direct message room.
|
||||||
*/
|
*/
|
||||||
fun setDirectMessage() {
|
fun setDirectMessage(): CreateRoomParams {
|
||||||
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
|
return copy(
|
||||||
isDirect = true
|
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
|
||||||
|
isDirect = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -215,28 +235,26 @@ class CreateRoomParams {
|
||||||
*/
|
*/
|
||||||
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
|
||||||
userId: String,
|
userId: String,
|
||||||
ids: List<String>) {
|
ids: List<String>): CreateRoomParams {
|
||||||
for (id in ids) {
|
return copy(
|
||||||
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
|
invite3pids = (invite3pids.orEmpty() + ids
|
||||||
if (null == invite3pids) {
|
.takeIf { hsConfig.identityServerUri != null }
|
||||||
invite3pids = ArrayList()
|
?.filter { id -> Patterns.EMAIL_ADDRESS.matcher(id).matches() }
|
||||||
}
|
?.map { id ->
|
||||||
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
|
Invite3Pid(
|
||||||
medium = ThreePidMedium.EMAIL,
|
idServer = hsConfig.identityServerUri!!.host!!,
|
||||||
address = id)
|
medium = ThreePidMedium.EMAIL,
|
||||||
|
address = id
|
||||||
invite3pids!!.add(pid)
|
)
|
||||||
} else if (isUserId(id)) {
|
}
|
||||||
// do not invite oneself
|
.orEmpty())
|
||||||
if (userId != id) {
|
.distinct(),
|
||||||
if (null == invitedUserIds) {
|
invitedUserIds = (invitedUserIds.orEmpty() + ids
|
||||||
invitedUserIds = ArrayList()
|
.filter { id -> isUserId(id) }
|
||||||
}
|
// do not invite oneself
|
||||||
|
.filter { id -> id != userId })
|
||||||
invitedUserIds!!.add(id)
|
.distinct()
|
||||||
}
|
)
|
||||||
}
|
// TODO add phonenumbers when it will be available
|
||||||
// TODO add phonenumbers when it will be available
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
interface MessageContent {
|
interface MessageContent {
|
||||||
|
// TODO Rename to msgType
|
||||||
val type: String
|
val type: String
|
||||||
val body: String
|
val body: String
|
||||||
val relatesTo: RelationDefaultContent?
|
val relatesTo: RelationDefaultContent?
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageRelationContent(
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
)
|
|
@ -26,6 +26,7 @@ object MessageType {
|
||||||
const val MSGTYPE_VIDEO = "m.video"
|
const val MSGTYPE_VIDEO = "m.video"
|
||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
|
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||||
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
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.room.model.relation.RelationDefaultContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationAcceptContent(
|
||||||
|
@Json(name = "hash") override val hash: String?,
|
||||||
|
@Json(name = "key_agreement_protocol") override val keyAgreementProtocol: String?,
|
||||||
|
@Json(name = "message_authentication_code") override val messageAuthenticationCode: String?,
|
||||||
|
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>?,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?,
|
||||||
|
@Json(name = "commitment") override var commitment: String? = null
|
||||||
|
) : VerificationInfoAccept {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank()
|
||||||
|
|| keyAgreementProtocol.isNullOrBlank()
|
||||||
|
|| hash.isNullOrBlank()
|
||||||
|
|| commitment.isNullOrBlank()
|
||||||
|
|| messageAuthenticationCode.isNullOrBlank()
|
||||||
|
|| shortAuthenticationStrings.isNullOrEmpty()) {
|
||||||
|
Timber.e("## received invalid verification request")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
|
companion object : VerificationInfoAcceptFactory {
|
||||||
|
|
||||||
|
override fun create(tid: String,
|
||||||
|
keyAgreementProtocol: String,
|
||||||
|
hash: String,
|
||||||
|
commitment: String,
|
||||||
|
messageAuthenticationCode: String,
|
||||||
|
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
|
||||||
|
return MessageVerificationAcceptContent(
|
||||||
|
hash,
|
||||||
|
keyAgreementProtocol,
|
||||||
|
messageAuthenticationCode,
|
||||||
|
shortAuthenticationStrings,
|
||||||
|
RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
),
|
||||||
|
commitment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
|
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.room.model.relation.RelationDefaultContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoCancel
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageVerificationCancelContent(
|
||||||
|
@Json(name = "code") override val code: String? = null,
|
||||||
|
@Json(name = "reason") override val reason: String? = null,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
|
||||||
|
) : VerificationInfoCancel {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent {
|
||||||
|
return MessageVerificationCancelContent(
|
||||||
|
reason.value,
|
||||||
|
reason.humanReadable,
|
||||||
|
RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
transactionId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
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.internal.crypto.verification.VerificationInfo
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationDoneContent(
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
) : VerificationInfo {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun isValid() = transactionID?.isNotEmpty() == true
|
||||||
|
|
||||||
|
override fun toEventContent(): Content? = toContent()
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
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.room.model.relation.RelationDefaultContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationKeyContent(
|
||||||
|
/**
|
||||||
|
* The device’s ephemeral public key, as an unpadded base64 string
|
||||||
|
*/
|
||||||
|
@Json(name = "key") override val key: String? = null,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
) : VerificationInfoKey {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
||||||
|
Timber.e("## received invalid verification request")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
|
companion object : VerificationInfoKeyFactory {
|
||||||
|
|
||||||
|
override fun create(tid: String, pubKey: String): VerificationInfoKey {
|
||||||
|
return MessageVerificationKeyContent(
|
||||||
|
pubKey,
|
||||||
|
RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
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.room.model.relation.RelationDefaultContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMacFactory
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationMacContent(
|
||||||
|
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
||||||
|
@Json(name = "keys") override val keys: String? = null,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
) : VerificationInfoMac {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : VerificationInfoMacFactory {
|
||||||
|
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
|
||||||
|
return MessageVerificationMacContent(
|
||||||
|
mac,
|
||||||
|
keys,
|
||||||
|
RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
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.room.model.relation.RelationDefaultContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.MessageVerificationReadyFactory
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationReadyContent(
|
||||||
|
@Json(name = "from_device") override val fromDevice: String? = null,
|
||||||
|
@Json(name = "methods") override val methods: List<String>? = null,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
) : VerificationInfoReady {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MessageVerificationReadyFactory {
|
||||||
|
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
|
||||||
|
return MessageVerificationReadyContent(
|
||||||
|
fromDevice = fromDevice,
|
||||||
|
methods = methods,
|
||||||
|
relatesTo = RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
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.internal.crypto.verification.VerificationInfoRequest
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageVerificationRequestContent(
|
||||||
|
@Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
||||||
|
@Json(name = "body") override val body: String,
|
||||||
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
|
@Json(name = "methods") override val methods: List<String>,
|
||||||
|
@Json(name = "to") val toUserId: String,
|
||||||
|
@Json(name = "timestamp") override val timestamp: Long?,
|
||||||
|
@Json(name = "format") val format: String? = null,
|
||||||
|
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
) : MessageContent, VerificationInfoRequest {
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||||
|
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.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||||
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationStartContent(
|
||||||
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
|
@Json(name = "hashes") override val hashes: List<String>?,
|
||||||
|
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>?,
|
||||||
|
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>?,
|
||||||
|
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>?,
|
||||||
|
@Json(name = "method") override val method: String?,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?,
|
||||||
|
@Json(name = "secret") override val sharedSecret: String?
|
||||||
|
) : VerificationInfoStart {
|
||||||
|
|
||||||
|
override fun toCanonicalJson(): String? {
|
||||||
|
return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
// TODO Move those method to the interface?
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank()
|
||||||
|
|| fromDevice.isNullOrBlank()
|
||||||
|
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
||||||
|
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
||||||
|
Timber.e("## received invalid verification request")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidSas(): Boolean {
|
||||||
|
if (keyAgreementProtocols.isNullOrEmpty()
|
||||||
|
|| hashes.isNullOrEmpty()
|
||||||
|
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||||
|
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||||
|
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||||
|
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||||
|
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidReciprocate(): Boolean {
|
||||||
|
if (sharedSecret.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
|
}
|
|
@ -30,3 +30,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
|
||||||
* Matrix algorithm value for megolm keys backup.
|
* Matrix algorithm value for megolm keys backup.
|
||||||
*/
|
*/
|
||||||
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"
|
||||||
|
|
||||||
|
// TODO Refacto: use this constants everywhere
|
||||||
|
const val ed25519 = "ed25519"
|
||||||
|
const val curve25519 = "curve25519"
|
||||||
|
|
|
@ -21,14 +21,68 @@ import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
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.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
|
||||||
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.store.db.RealmCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.*
|
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUsers
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultEncryptEventTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadKeysTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadSignaturesTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadSigningKeysTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.EncryptEventTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||||
|
@ -132,6 +186,12 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask
|
abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUploadSigningKeysTask(uploadKeysTask: DefaultUploadSigningKeysTask): UploadSigningKeysTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUploadSignaturesTask(uploadSignaturesTask: DefaultUploadSignaturesTask): UploadSignaturesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
||||||
|
|
||||||
|
@ -180,6 +240,12 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
||||||
: ClaimOneTimeKeysForUsersDeviceTask
|
: ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
@ -187,4 +253,7 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
|
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
|
||||||
: DeleteDeviceWithUserPasswordTask
|
: DeleteDeviceWithUserPasswordTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.crypto
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
@ -44,7 +45,10 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct
|
||||||
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.algorithms.megolm.MXMegolmEncryptionFactory
|
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
|
||||||
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.DeviceTrustLevel
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||||
|
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
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
|
@ -54,6 +58,7 @@ 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.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.toRest
|
||||||
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.DeleteDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
||||||
|
@ -62,7 +67,7 @@ import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
@ -123,8 +128,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
||||||
//
|
//
|
||||||
private val roomDecryptorProvider: RoomDecryptorProvider,
|
private val roomDecryptorProvider: RoomDecryptorProvider,
|
||||||
// The SAS verification service.
|
// The verification service.
|
||||||
private val sasVerificationService: DefaultSasVerificationService,
|
private val verificationService: DefaultVerificationService,
|
||||||
|
|
||||||
|
private val crossSigningService: DefaultCrossSigningService,
|
||||||
//
|
//
|
||||||
private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager,
|
private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager,
|
||||||
//
|
//
|
||||||
|
@ -150,6 +157,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val cryptoCoroutineScope: CoroutineScope
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
|
init {
|
||||||
|
verificationService.cryptoService = this
|
||||||
|
}
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
// MXEncrypting instance for each room.
|
// MXEncrypting instance for each room.
|
||||||
|
@ -176,7 +187,17 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||||
setDeviceNameTask
|
setDeviceNameTask
|
||||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||||
this.callback = callback
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
// bg refresh of crypto device
|
||||||
|
downloadKeys(listOf(credentials.userId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {})
|
||||||
|
callback.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
callback.onFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -201,7 +222,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
|
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMyDevice(): MXDeviceInfo {
|
override fun getMyDevice(): CryptoDeviceInfo {
|
||||||
return myDeviceInfoHolder.get().myDevice
|
return myDeviceInfoHolder.get().myDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,9 +342,11 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
override fun getKeysBackupService() = keysBackup
|
override fun getKeysBackupService() = keysBackup
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the SasVerificationService
|
* @return the VerificationService
|
||||||
*/
|
*/
|
||||||
override fun getSasVerificationService() = sasVerificationService
|
override fun getVerificationService() = verificationService
|
||||||
|
|
||||||
|
override fun getCrossSigningService() = crossSigningService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sync response has been received
|
* A sync response has been received
|
||||||
|
@ -357,7 +380,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
* @param algorithm the encryption algorithm.
|
* @param algorithm the encryption algorithm.
|
||||||
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
||||||
*/
|
*/
|
||||||
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
|
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo? {
|
||||||
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
||||||
// We only deal in olm keys
|
// We only deal in olm keys
|
||||||
null
|
null
|
||||||
|
@ -370,13 +393,28 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
*/
|
*/
|
||||||
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
|
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
||||||
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||||
cryptoStore.getUserDevice(deviceId, userId)
|
cryptoStore.getUserDevice(userId, deviceId)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
|
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||||
|
return cryptoStore.getLiveDeviceList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
||||||
|
return cryptoStore.getLiveDeviceList(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||||
|
return cryptoStore.getLiveDeviceList(userIds)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the devices as known
|
* Set the devices as known
|
||||||
|
@ -401,7 +439,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
// assume if the device is either verified or blocked
|
// assume if the device is either verified or blocked
|
||||||
// it means that the device is known
|
// it means that the device is known
|
||||||
if (device?.isUnknown == true) {
|
if (device?.isUnknown == true) {
|
||||||
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
|
device.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||||
isUpdated = true
|
isUpdated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,12 +456,12 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Update the blocked/verified state of the given device.
|
* Update the blocked/verified state of the given device.
|
||||||
*
|
*
|
||||||
* @param verificationStatus the new verification status
|
* @param trustLevel the new trust level
|
||||||
* @param deviceId the unique identifier for the device.
|
* @param userId the owner of the device
|
||||||
* @param userId the owner of the device
|
* @param deviceId the unique identifier for the device.
|
||||||
*/
|
*/
|
||||||
override fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String) {
|
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||||
setDeviceVerificationAction.handle(verificationStatus, deviceId, userId)
|
setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -504,9 +542,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* @return the stored device keys for a user.
|
* @return the stored device keys for a user.
|
||||||
*/
|
*/
|
||||||
override fun getUserDevices(userId: String): MutableList<MXDeviceInfo> {
|
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||||
val map = cryptoStore.getUserDevices(userId)
|
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
||||||
return if (null != map) ArrayList(map.values) else ArrayList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEncryptionEnabledForInvitedUser(): Boolean {
|
fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||||
|
@ -768,11 +805,15 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
// Prepare the device keys data to send
|
// Prepare the device keys data to send
|
||||||
// Sign it
|
// Sign it
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
||||||
getMyDevice().signatures = objectSigner.signObject(canonicalJson)
|
var rest = getMyDevice().toRest()
|
||||||
|
|
||||||
|
rest = rest.copy(
|
||||||
|
signatures = objectSigner.signObject(canonicalJson)
|
||||||
|
)
|
||||||
|
|
||||||
// For now, we set the device id explicitly, as we may not be using the
|
// For now, we set the device id explicitly, as we may not be using the
|
||||||
// same one as used in login.
|
// same one as used in login.
|
||||||
val uploadDeviceKeysParams = UploadKeysTask.Params(getMyDevice().toDeviceKeys(), null, getMyDevice().deviceId)
|
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, getMyDevice().deviceId)
|
||||||
return uploadKeysTask.execute(uploadDeviceKeysParams)
|
return uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1012,8 +1053,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
* @param devicesInRoom the devices map
|
* @param devicesInRoom the devices map
|
||||||
* @return the unknown devices map
|
* @return the unknown devices map
|
||||||
*/
|
*/
|
||||||
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXUsersDevicesMap<MXDeviceInfo> {
|
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
val userIds = devicesInRoom.userIds
|
val userIds = devicesInRoom.userIds
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId ->
|
devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId ->
|
||||||
|
@ -1028,7 +1069,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
return unknownDevices
|
return unknownDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
runCatching {
|
runCatching {
|
||||||
deviceListManager.downloadKeys(userIds, forceDownload)
|
deviceListManager.downloadKeys(userIds, forceDownload)
|
||||||
|
|
|
@ -19,7 +19,9 @@ package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoInfoMapper
|
||||||
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.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
|
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||||
|
@ -36,6 +38,36 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
||||||
|
|
||||||
|
interface UserDevicesUpdateListener {
|
||||||
|
fun onUsersDeviceUpdate(users: List<String>)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
|
||||||
|
|
||||||
|
fun addListener(listener: UserDevicesUpdateListener) {
|
||||||
|
synchronized(deviceChangeListeners) {
|
||||||
|
deviceChangeListeners.add(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeListener(listener: UserDevicesUpdateListener) {
|
||||||
|
synchronized(deviceChangeListeners) {
|
||||||
|
deviceChangeListeners.remove(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchDeviceChange(users: List<String>) {
|
||||||
|
synchronized(deviceChangeListeners) {
|
||||||
|
deviceChangeListeners.forEach {
|
||||||
|
try {
|
||||||
|
it.onUsersDeviceUpdate(users)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "Failed to dispatch device change")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HS not ready for retry
|
// HS not ready for retry
|
||||||
private val notReadyToRetryHS = mutableSetOf<String>()
|
private val notReadyToRetryHS = mutableSetOf<String>()
|
||||||
|
|
||||||
|
@ -166,13 +198,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
* @param userIds the userIds list
|
* @param userIds the userIds list
|
||||||
* @param failures the failure map.
|
* @param failures the failure map.
|
||||||
*/
|
*/
|
||||||
private fun onKeysDownloadSucceed(userIds: List<String>, failures: Map<String, Map<String, Any>>?): MXUsersDevicesMap<MXDeviceInfo> {
|
private fun onKeysDownloadSucceed(userIds: List<String>, failures: Map<String, Map<String, Any>>?): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
if (failures != null) {
|
if (failures != null) {
|
||||||
for ((k, value) in failures) {
|
for ((k, value) in failures) {
|
||||||
val statusCode = when (val status = value["status"]) {
|
val statusCode = when (val status = value["status"]) {
|
||||||
is Double -> status.toInt()
|
is Double -> status.toInt()
|
||||||
is Int -> status.toInt()
|
is Int -> status.toInt()
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
if (statusCode == 503) {
|
if (statusCode == 503) {
|
||||||
synchronized(notReadyToRetryHS) {
|
synchronized(notReadyToRetryHS) {
|
||||||
|
@ -182,7 +214,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||||
val usersDevicesInfoMap = MXUsersDevicesMap<MXDeviceInfo>()
|
val usersDevicesInfoMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val devices = cryptoStore.getUserDevices(userId)
|
val devices = cryptoStore.getUserDevices(userId)
|
||||||
if (null == devices) {
|
if (null == devices) {
|
||||||
|
@ -207,6 +239,8 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||||
|
|
||||||
|
dispatchDeviceChange(userIds)
|
||||||
return usersDevicesInfoMap
|
return usersDevicesInfoMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,10 +251,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
* @param userIds The users to fetch.
|
* @param userIds The users to fetch.
|
||||||
* @param forceDownload Always download the keys even if cached.
|
* @param forceDownload Always download the keys even if cached.
|
||||||
*/
|
*/
|
||||||
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
|
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
||||||
// Map from userId -> deviceId -> MXDeviceInfo
|
// Map from userId -> deviceId -> MXDeviceInfo
|
||||||
val stored = MXUsersDevicesMap<MXDeviceInfo>()
|
val stored = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
|
|
||||||
// List of user ids we need to download keys for
|
// List of user ids we need to download keys for
|
||||||
val downloadUsers = ArrayList<String>()
|
val downloadUsers = ArrayList<String>()
|
||||||
|
@ -265,7 +299,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
*
|
*
|
||||||
* @param downloadUsers the user ids list
|
* @param downloadUsers the user ids list
|
||||||
*/
|
*/
|
||||||
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
|
private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
|
||||||
// get the user ids which did not already trigger a keys download
|
// get the user ids which did not already trigger a keys download
|
||||||
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
|
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
|
||||||
|
@ -283,39 +317,63 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
}
|
}
|
||||||
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
||||||
for (userId in filteredUsers) {
|
for (userId in filteredUsers) {
|
||||||
val devices = response.deviceKeys?.get(userId)
|
// al devices =
|
||||||
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
|
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
||||||
if (devices != null) {
|
|
||||||
val mutableDevices = devices.toMutableMap()
|
|
||||||
for ((deviceId, deviceInfo) in devices) {
|
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
||||||
|
if (!models.isNullOrEmpty()) {
|
||||||
|
val workingCopy = models.toMutableMap()
|
||||||
|
for ((deviceId, deviceInfo) in models) {
|
||||||
// Get the potential previously store device keys for this device
|
// Get the potential previously store device keys for this device
|
||||||
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
|
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
|
|
||||||
// in some race conditions (like unit tests)
|
// in some race conditions (like unit tests)
|
||||||
// the self device must be seen as verified
|
// the self device must be seen as verified
|
||||||
if (deviceInfo.deviceId == credentials.deviceId && userId == credentials.userId) {
|
if (deviceInfo.deviceId == credentials.deviceId && userId == credentials.userId) {
|
||||||
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
|
deviceInfo.trustLevel = DeviceTrustLevel(previouslyStoredDeviceKeys?.trustLevel?.crossSigningVerified ?: false, true)
|
||||||
}
|
}
|
||||||
// Validate received keys
|
// Validate received keys
|
||||||
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
|
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
|
||||||
// New device keys are not valid. Do not store them
|
// New device keys are not valid. Do not store them
|
||||||
mutableDevices.remove(deviceId)
|
workingCopy.remove(deviceId)
|
||||||
if (null != previouslyStoredDeviceKeys) {
|
if (null != previouslyStoredDeviceKeys) {
|
||||||
// But keep old validated ones if any
|
// But keep old validated ones if any
|
||||||
mutableDevices[deviceId] = previouslyStoredDeviceKeys
|
workingCopy[deviceId] = previouslyStoredDeviceKeys
|
||||||
}
|
}
|
||||||
} else if (null != previouslyStoredDeviceKeys) {
|
} else if (null != previouslyStoredDeviceKeys) {
|
||||||
// The verified status is not sync'ed with hs.
|
// The verified status is not sync'ed with hs.
|
||||||
// This is a client side information, valid only for this client.
|
// This is a client side information, valid only for this client.
|
||||||
// So, transfer its previous value
|
// So, transfer its previous value
|
||||||
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
|
workingCopy[deviceId]!!.trustLevel = previouslyStoredDeviceKeys.trustLevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update the store
|
// Update the store
|
||||||
// Note that devices which aren't in the response will be removed from the stores
|
// Note that devices which aren't in the response will be removed from the stores
|
||||||
cryptoStore.storeUserDevices(userId, mutableDevices)
|
cryptoStore.storeUserDevices(userId, workingCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle cross signing keys update
|
||||||
|
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
|
||||||
|
Timber.d("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
|
||||||
|
}
|
||||||
|
val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also {
|
||||||
|
Timber.d("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
|
||||||
|
}
|
||||||
|
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
|
||||||
|
Timber.d("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
|
||||||
|
}
|
||||||
|
cryptoStore.storeUserCrossSigningKeys(
|
||||||
|
userId,
|
||||||
|
masterKey,
|
||||||
|
selfSigningKey,
|
||||||
|
userSigningKey
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update devices trust for these users
|
||||||
|
dispatchDeviceChange(downloadUsers)
|
||||||
|
|
||||||
return onKeysDownloadSucceed(filteredUsers, response.failures)
|
return onKeysDownloadSucceed(filteredUsers, response.failures)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +387,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
* @param previouslyStoredDeviceKeys the device keys we received before for this device
|
* @param previouslyStoredDeviceKeys the device keys we received before for this device
|
||||||
* @return true if succeeds
|
* @return true if succeeds
|
||||||
*/
|
*/
|
||||||
private fun validateDeviceKeys(deviceKeys: MXDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: MXDeviceInfo?): Boolean {
|
private fun validateDeviceKeys(deviceKeys: CryptoDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: CryptoDeviceInfo?): Boolean {
|
||||||
if (null == deviceKeys) {
|
if (null == deviceKeys) {
|
||||||
Timber.e("## validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
|
Timber.e("## validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
|
||||||
return false
|
return false
|
||||||
|
@ -357,14 +415,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
}
|
}
|
||||||
|
|
||||||
val signKeyId = "ed25519:" + deviceKeys.deviceId
|
val signKeyId = "ed25519:" + deviceKeys.deviceId
|
||||||
val signKey = deviceKeys.keys?.get(signKeyId)
|
val signKey = deviceKeys.keys[signKeyId]
|
||||||
|
|
||||||
if (null == signKey) {
|
if (null == signKey) {
|
||||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
|
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val signatureMap = deviceKeys.signatures?.get(userId)
|
val signatureMap = deviceKeys.signatures[userId]
|
||||||
|
|
||||||
if (null == signatureMap) {
|
if (null == signatureMap) {
|
||||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
|
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
|
||||||
|
|
|
@ -107,7 +107,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||||
}
|
}
|
||||||
// if the device is verified already, share the keys
|
// if the device is verified already, share the keys
|
||||||
val device = cryptoStore.getUserDevice(deviceId!!, userId)
|
val device = cryptoStore.getUserDevice(userId, deviceId!!)
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
if (device.isVerified) {
|
if (device.isVerified) {
|
||||||
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
|
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
|
||||||
|
@ -122,6 +122,14 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If cross signing is available on account we automatically discard untrust devices request
|
||||||
|
if (cryptoStore.getMyCrossSigningInfo() != null) {
|
||||||
|
// At this point the device is unknown, we don't want to bother user with that
|
||||||
|
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
cryptoStore.storeIncomingRoomKeyRequest(request)
|
cryptoStore.storeIncomingRoomKeyRequest(request)
|
||||||
onRoomKeyRequest(request)
|
onRoomKeyRequest(request)
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
*/
|
*/
|
||||||
fun release() {
|
fun release() {
|
||||||
olmAccount?.releaseAccount()
|
olmAccount?.releaseAccount()
|
||||||
|
olmUtility?.releaseUtility()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
package im.vector.matrix.android.internal.crypto
|
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.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
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
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -35,11 +36,13 @@ internal class MyDeviceInfoHolder @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* my device info
|
* my device info
|
||||||
*/
|
*/
|
||||||
val myDevice: MXDeviceInfo = MXDeviceInfo(credentials.deviceId!!, credentials.userId)
|
val myDevice: CryptoDeviceInfo
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
val keys = HashMap<String, String>()
|
val keys = HashMap<String, String>()
|
||||||
|
|
||||||
|
// TODO it's a bit strange, why not load from DB?
|
||||||
if (!olmDevice.deviceEd25519Key.isNullOrEmpty()) {
|
if (!olmDevice.deviceEd25519Key.isNullOrEmpty()) {
|
||||||
keys["ed25519:" + credentials.deviceId] = olmDevice.deviceEd25519Key!!
|
keys["ed25519:" + credentials.deviceId] = olmDevice.deviceEd25519Key!!
|
||||||
}
|
}
|
||||||
|
@ -48,10 +51,22 @@ internal class MyDeviceInfoHolder @Inject constructor(
|
||||||
keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!!
|
keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!!
|
||||||
}
|
}
|
||||||
|
|
||||||
myDevice.keys = keys
|
// myDevice.keys = keys
|
||||||
|
//
|
||||||
|
// myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
|
||||||
|
|
||||||
myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
|
// TODO hwo to really check cross signed status?
|
||||||
myDevice.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
|
//
|
||||||
|
val crossSigned = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.trustLevel?.locallyVerified ?: false
|
||||||
|
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
|
||||||
|
|
||||||
|
myDevice = CryptoDeviceInfo(
|
||||||
|
credentials.deviceId!!,
|
||||||
|
credentials.userId,
|
||||||
|
keys = keys,
|
||||||
|
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
||||||
|
trustLevel = DeviceTrustLevel(crossSigned, true)
|
||||||
|
)
|
||||||
|
|
||||||
// Add our own deviceinfo to the store
|
// Add our own deviceinfo to the store
|
||||||
val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId)
|
val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId)
|
||||||
|
|
|
@ -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.MXOlmDevice
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
|
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
@ -28,8 +28,8 @@ import javax.inject.Inject
|
||||||
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val olmDevice: MXOlmDevice,
|
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val olmDevice: MXOlmDevice,
|
||||||
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
|
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
|
||||||
|
|
||||||
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
|
suspend fun handle(devicesByUser: Map<String, List<CryptoDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||||
val devicesWithoutSession = ArrayList<MXDeviceInfo>()
|
val devicesWithoutSession = ArrayList<CryptoDeviceInfo>()
|
||||||
|
|
||||||
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
|
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? {
|
||||||
var sessionId: String? = null
|
var sessionId: String? = null
|
||||||
|
|
||||||
val deviceId = deviceInfo.deviceId
|
val deviceId = deviceInfo.deviceId
|
||||||
|
|
|
@ -40,7 +40,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
|
||||||
// Don't bother setting up session to ourself
|
// Don't bother setting up session to ourself
|
||||||
it.identityKey() != olmDevice.deviceCurve25519Key
|
it.identityKey() != olmDevice.deviceCurve25519Key
|
||||||
// Don't bother setting up sessions with blocked users
|
// Don't bother setting up sessions with blocked users
|
||||||
&& !it.isVerified
|
&& !(it.trustLevel?.isVerified() ?: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.crypto.actions
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_OLM
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_OLM
|
||||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage
|
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.convertToUTF8
|
import im.vector.matrix.android.internal.util.convertToUTF8
|
||||||
|
@ -37,7 +37,7 @@ internal class MessageEncrypter @Inject constructor(private val credentials: Cre
|
||||||
* @param deviceInfos list of device infos to encrypt for.
|
* @param deviceInfos list of device infos to encrypt for.
|
||||||
* @return the content for an m.room.encrypted event.
|
* @return the content for an m.room.encrypted event.
|
||||||
*/
|
*/
|
||||||
fun encryptMessage(payloadFields: Map<String, Any>, deviceInfos: List<MXDeviceInfo>): EncryptedMessage {
|
fun encryptMessage(payloadFields: Map<String, Any>, deviceInfos: List<CryptoDeviceInfo>): EncryptedMessage {
|
||||||
val deviceInfoParticipantKey = deviceInfos.associateBy { it.identityKey()!! }
|
val deviceInfoParticipantKey = deviceInfos.associateBy { it.identityKey()!! }
|
||||||
|
|
||||||
val payloadJson = payloadFields.toMutableMap()
|
val payloadJson = payloadFields.toMutableMap()
|
||||||
|
|
|
@ -16,6 +16,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.keysbackup.KeysBackup
|
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||||
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
|
||||||
|
@ -27,8 +28,8 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val keysBackup: KeysBackup) {
|
private val keysBackup: KeysBackup) {
|
||||||
|
|
||||||
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
|
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||||
val device = cryptoStore.getUserDevice(deviceId, userId)
|
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
if (null == device) {
|
if (null == device) {
|
||||||
|
@ -36,10 +37,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device.verified != verificationStatus) {
|
if (device.isVerified != trustLevel.isVerified()) {
|
||||||
device.verified = verificationStatus
|
|
||||||
cryptoStore.storeUserDevice(userId, device)
|
|
||||||
|
|
||||||
if (userId == this.userId) {
|
if (userId == this.userId) {
|
||||||
// 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
|
||||||
|
@ -47,5 +45,10 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
keysBackup.checkAndStartKeysBackup()
|
keysBackup.checkAndStartKeysBackup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (device.trustLevel != trustLevel) {
|
||||||
|
device.trustLevel = trustLevel
|
||||||
|
cryptoStore.storeUserDevice(userId, device)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,12 @@ import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
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.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.internal.crypto.*
|
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||||
|
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
|
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||||
|
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
|
||||||
|
@ -59,7 +64,10 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||||
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
||||||
|
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
return decryptEvent(event, timeline, true)
|
// If cross signing is enabled, we don't send request until the keys are trusted
|
||||||
|
// There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
|
||||||
|
val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
|
||||||
|
return decryptEvent(event, timeline, requestOnFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
||||||
|
@ -297,7 +305,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||||
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||||
.mapCatching {
|
.mapCatching {
|
||||||
val deviceId = request.deviceId
|
val deviceId = request.deviceId
|
||||||
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
|
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "")
|
||||||
if (deviceInfo == null) {
|
if (deviceInfo == null) {
|
||||||
throw RuntimeException()
|
throw RuntimeException()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
|
||||||
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.KeysBackup
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
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
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
@ -95,7 +95,7 @@ internal class MXMegolmEncryption(
|
||||||
*
|
*
|
||||||
* @param devicesInRoom the devices list
|
* @param devicesInRoom the devices list
|
||||||
*/
|
*/
|
||||||
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
|
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
|
||||||
var session = outboundSession
|
var session = outboundSession
|
||||||
if (session == null
|
if (session == null
|
||||||
// Need to make a brand new session?
|
// Need to make a brand new session?
|
||||||
|
@ -106,7 +106,7 @@ internal class MXMegolmEncryption(
|
||||||
outboundSession = session
|
outboundSession = session
|
||||||
}
|
}
|
||||||
val safeSession = session
|
val safeSession = session
|
||||||
val shareMap = HashMap<String, MutableList<MXDeviceInfo>>()/* userId */
|
val shareMap = HashMap<String, MutableList<CryptoDeviceInfo>>()/* userId */
|
||||||
val userIds = devicesInRoom.userIds
|
val userIds = devicesInRoom.userIds
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
|
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
|
||||||
|
@ -129,14 +129,14 @@ internal class MXMegolmEncryption(
|
||||||
* @param devicesByUsers the devices map
|
* @param devicesByUsers the devices map
|
||||||
*/
|
*/
|
||||||
private suspend fun shareKey(session: MXOutboundSessionInfo,
|
private suspend fun shareKey(session: MXOutboundSessionInfo,
|
||||||
devicesByUsers: Map<String, List<MXDeviceInfo>>) {
|
devicesByUsers: Map<String, List<CryptoDeviceInfo>>) {
|
||||||
// nothing to send, the task is done
|
// nothing to send, the task is done
|
||||||
if (devicesByUsers.isEmpty()) {
|
if (devicesByUsers.isEmpty()) {
|
||||||
Timber.v("## shareKey() : nothing more to do")
|
Timber.v("## shareKey() : nothing more to do")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
||||||
val subMap = HashMap<String, List<MXDeviceInfo>>()
|
val subMap = HashMap<String, List<CryptoDeviceInfo>>()
|
||||||
var devicesCount = 0
|
var devicesCount = 0
|
||||||
for ((userId, devices) in devicesByUsers) {
|
for ((userId, devices) in devicesByUsers) {
|
||||||
subMap[userId] = devices
|
subMap[userId] = devices
|
||||||
|
@ -158,7 +158,7 @@ internal class MXMegolmEncryption(
|
||||||
* @param devicesByUser the devices map
|
* @param devicesByUser the devices map
|
||||||
*/
|
*/
|
||||||
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
|
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
|
||||||
devicesByUser: Map<String, List<MXDeviceInfo>>) {
|
devicesByUser: Map<String, List<CryptoDeviceInfo>>) {
|
||||||
val sessionKey = olmDevice.getSessionKey(session.sessionId)
|
val sessionKey = olmDevice.getSessionKey(session.sessionId)
|
||||||
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
|
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ internal class MXMegolmEncryption(
|
||||||
*
|
*
|
||||||
* @param userIds the user ids whose devices must be checked.
|
* @param userIds the user ids whose devices must be checked.
|
||||||
*/
|
*/
|
||||||
private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<MXDeviceInfo> {
|
private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
// We are happy to use a cached version here: we assume that if we already
|
// We are happy to use a cached version here: we assume that if we already
|
||||||
// have a list of the user's devices, then we already share an e2e room
|
// have a list of the user's devices, then we already share an e2e room
|
||||||
// with them, which means that they will have announced any new devices via
|
// with them, which means that they will have announced any new devices via
|
||||||
|
@ -271,8 +271,8 @@ internal class MXMegolmEncryption(
|
||||||
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|
||||||
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
||||||
|
|
||||||
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
|
val devicesInRoom = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
|
|
||||||
for (userId in keys.userIds) {
|
for (userId in keys.userIds) {
|
||||||
val deviceIds = keys.getUserDeviceIds(userId) ?: continue
|
val deviceIds = keys.getUserDeviceIds(userId) ?: continue
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
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 timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ internal class MXOutboundSessionInfo(
|
||||||
* @param devicesInRoom the devices map
|
* @param devicesInRoom the devices map
|
||||||
* @return true if we have shared the session with devices which aren't in devicesInRoom.
|
* @return true if we have shared the session with devices which aren't in devicesInRoom.
|
||||||
*/
|
*/
|
||||||
fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Boolean {
|
fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): Boolean {
|
||||||
val userIds = sharedWithDevices.userIds
|
val userIds = sharedWithDevices.userIds
|
||||||
|
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction
|
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction
|
||||||
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.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
|
||||||
internal class MXOlmEncryption(
|
internal class MXOlmEncryption(
|
||||||
|
@ -42,7 +42,7 @@ internal class MXOlmEncryption(
|
||||||
//
|
//
|
||||||
// TODO: there is a race condition here! What if a new user turns up
|
// TODO: there is a race condition here! What if a new user turns up
|
||||||
ensureSession(userIds)
|
ensureSession(userIds)
|
||||||
val deviceInfos = ArrayList<MXDeviceInfo>()
|
val deviceInfos = ArrayList<CryptoDeviceInfo>()
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
||||||
for (device in devices) {
|
for (device in devices) {
|
||||||
|
|
|
@ -65,6 +65,33 @@ internal interface CryptoApi {
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query")
|
||||||
fun downloadKeysForUsers(@Body params: KeysQueryBody): Call<KeysQueryResponse>
|
fun downloadKeysForUsers(@Body params: KeysQueryBody): Call<KeysQueryResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CrossSigning - Uploading signing keys
|
||||||
|
* Public keys for the cross-signing keys are uploaded to the servers using /keys/device_signing/upload.
|
||||||
|
* This endpoint requires UI Auth.
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload")
|
||||||
|
fun uploadSigningKeys(@Body params: UploadSigningKeysBody): Call<KeysQueryResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CrossSigning - Uploading signatures
|
||||||
|
* Signatures of device keys can be up
|
||||||
|
* loaded using /keys/signatures/upload.
|
||||||
|
* For example, Alice signs one of her devices (HIJKLMN) (using her self-signing key),
|
||||||
|
* her own master key (using her HIJKLMN device), Bob's master key (using her user-signing key).
|
||||||
|
*
|
||||||
|
* The response contains a failures property, which is a map of user ID to device ID to failure reason, if any of the uploaded keys failed.
|
||||||
|
* The homeserver should verify that the signatures on the uploaded keys are valid.
|
||||||
|
* If a signature is not valid, the homeserver should set the corresponding entry in failures to a JSON object
|
||||||
|
* with the errcode property set to M_INVALID_SIGNATURE.
|
||||||
|
*
|
||||||
|
* After Alice uploads a signature for her own devices or master key,
|
||||||
|
* her signature will be included in the results of the /keys/query request when anyone requests her keys.
|
||||||
|
* However, signatures made for other users' keys, made by her user-signing key, will not be included.
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload")
|
||||||
|
fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): Call<SignatureUploadResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Claim one-time keys.
|
* Claim one-time keys.
|
||||||
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim
|
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim
|
||||||
|
|
|
@ -0,0 +1,690 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||||
|
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.KeyUsage
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import org.matrix.olm.OlmPkSigning
|
||||||
|
import org.matrix.olm.OlmUtility
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class DefaultCrossSigningService @Inject constructor(
|
||||||
|
@UserId private val userId: String,
|
||||||
|
private val cryptoStore: IMXCryptoStore,
|
||||||
|
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
|
||||||
|
private val olmDevice: MXOlmDevice,
|
||||||
|
private val deviceListManager: DeviceListManager,
|
||||||
|
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||||
|
private val uploadSignaturesTask: UploadSignaturesTask,
|
||||||
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val taskExecutor: TaskExecutor) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
||||||
|
|
||||||
|
private var olmUtility: OlmUtility? = null
|
||||||
|
|
||||||
|
private var masterPkSigning: OlmPkSigning? = null
|
||||||
|
private var userPkSigning: OlmPkSigning? = null
|
||||||
|
private var selfSigningPkSigning: OlmPkSigning? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
olmUtility = OlmUtility()
|
||||||
|
|
||||||
|
// Try to get stored keys if they exist
|
||||||
|
cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo ->
|
||||||
|
Timber.i("## CrossSigning - Found Existing self signed keys")
|
||||||
|
Timber.i("## CrossSigning - Checking if private keys are known")
|
||||||
|
|
||||||
|
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo ->
|
||||||
|
privateKeysInfo.master
|
||||||
|
?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
|
masterPkSigning = pkSigning
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
Timber.w("## CrossSigning - Public master key does not match the private key")
|
||||||
|
// TODO untrust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
privateKeysInfo.user
|
||||||
|
?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
|
userPkSigning = pkSigning
|
||||||
|
Timber.i("## CrossSigning - Loading User Signing key success")
|
||||||
|
} else {
|
||||||
|
Timber.w("## CrossSigning - Public User key does not match the private key")
|
||||||
|
// TODO untrust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
privateKeysInfo.selfSigned
|
||||||
|
?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
|
selfSigningPkSigning = pkSigning
|
||||||
|
Timber.i("## CrossSigning - Loading Self Signing key success")
|
||||||
|
} else {
|
||||||
|
Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
|
||||||
|
// TODO untrust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover local trust in case private key are there?
|
||||||
|
setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified())
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// Mmm this kind of a big issue
|
||||||
|
Timber.e(e, "Failed to initialize Cross Signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceListManager.addListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun release() {
|
||||||
|
olmUtility?.releaseUtility()
|
||||||
|
listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
|
||||||
|
deviceListManager.removeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun finalize() {
|
||||||
|
release()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Make 3 key pairs (MSK, USK, SSK)
|
||||||
|
* - Save the private keys with proper security
|
||||||
|
* - Sign the keys and upload them
|
||||||
|
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures
|
||||||
|
*/
|
||||||
|
override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>?) {
|
||||||
|
Timber.d("## CrossSigning initializeCrossSigning")
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// MASTER KEY
|
||||||
|
// =================
|
||||||
|
val masterPkOlm = OlmPkSigning()
|
||||||
|
val masterKeyPrivateKey = OlmPkSigning.generateSeed()
|
||||||
|
val masterPublicKey = masterPkOlm.initWithSeed(masterKeyPrivateKey)
|
||||||
|
|
||||||
|
Timber.v("## CrossSigning - masterPublicKey:$masterPublicKey")
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// USER KEY
|
||||||
|
// =================
|
||||||
|
val userSigningPkOlm = OlmPkSigning()
|
||||||
|
val uskPrivateKey = OlmPkSigning.generateSeed()
|
||||||
|
val uskPublicKey = userSigningPkOlm.initWithSeed(uskPrivateKey)
|
||||||
|
|
||||||
|
Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey")
|
||||||
|
|
||||||
|
// Sign userSigningKey with master
|
||||||
|
val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING)
|
||||||
|
.key(uskPublicKey)
|
||||||
|
.build()
|
||||||
|
.canonicalSignable()
|
||||||
|
.let { masterPkOlm.sign(it) }
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// SELF SIGNING KEY
|
||||||
|
// =================
|
||||||
|
val selfSigningPkOlm = OlmPkSigning()
|
||||||
|
val sskPrivateKey = OlmPkSigning.generateSeed()
|
||||||
|
val sskPublicKey = selfSigningPkOlm.initWithSeed(sskPrivateKey)
|
||||||
|
|
||||||
|
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
|
||||||
|
|
||||||
|
// Sign userSigningKey with master
|
||||||
|
val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
||||||
|
.key(sskPublicKey)
|
||||||
|
.build().signalableJSONDictionary()).let { masterPkOlm.sign(it) }
|
||||||
|
|
||||||
|
// I need to upload the keys
|
||||||
|
val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER)
|
||||||
|
.key(masterPublicKey)
|
||||||
|
.build()
|
||||||
|
val params = UploadSigningKeysTask.Params(
|
||||||
|
masterKey = mskCrossSigningKeyInfo,
|
||||||
|
userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING)
|
||||||
|
.key(uskPublicKey)
|
||||||
|
.signature(userId, masterPublicKey, signedUSK)
|
||||||
|
.build(),
|
||||||
|
selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
||||||
|
.key(sskPublicKey)
|
||||||
|
.signature(userId, masterPublicKey, signedSSK)
|
||||||
|
.build(),
|
||||||
|
userPasswordAuth = authParams
|
||||||
|
)
|
||||||
|
|
||||||
|
this.masterPkSigning = masterPkOlm
|
||||||
|
this.userPkSigning = userSigningPkOlm
|
||||||
|
this.selfSigningPkSigning = selfSigningPkOlm
|
||||||
|
|
||||||
|
val crossSigningInfo = MXCrossSigningInfo(userId, listOf(params.masterKey, params.userKey, params.selfSignedKey))
|
||||||
|
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
|
||||||
|
setUserKeysAsTrusted(userId, true)
|
||||||
|
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
||||||
|
|
||||||
|
uploadSigningKeysTask.configureWith(params) {
|
||||||
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
Timber.i("## CrossSigning - Keys successfully uploaded")
|
||||||
|
|
||||||
|
// Sign the current device with SSK
|
||||||
|
val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder()
|
||||||
|
|
||||||
|
val myDevice = myDeviceInfoHolder.get().myDevice
|
||||||
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
|
||||||
|
val signedDevice = selfSigningPkOlm.sign(canonicalJson)
|
||||||
|
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also {
|
||||||
|
it[userId] = (it[userId]
|
||||||
|
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
|
||||||
|
}
|
||||||
|
myDevice.copy(signatures = updateSignatures).let {
|
||||||
|
uploadSignatureQueryBuilder.withDeviceInfo(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign MSK with device key (migration) and upload signatures
|
||||||
|
olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign ->
|
||||||
|
val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap()
|
||||||
|
?: HashMap()).also {
|
||||||
|
it[userId] = (it[userId]
|
||||||
|
?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign)
|
||||||
|
}
|
||||||
|
mskCrossSigningKeyInfo.copy(
|
||||||
|
signatures = mskUpdatedSignatures
|
||||||
|
).let {
|
||||||
|
uploadSignatureQueryBuilder.withSigningKeyInfo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTrustOnKeyChange()
|
||||||
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
|
||||||
|
// this.retryCount = 3
|
||||||
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
Timber.i("## CrossSigning - signatures successfully uploaded")
|
||||||
|
callback?.onSuccess(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
// Clear
|
||||||
|
Timber.e(failure, "## CrossSigning - Failed to upload signatures")
|
||||||
|
clearSigningKeys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
Timber.e(failure, "## CrossSigning - Failed to upload signing keys")
|
||||||
|
clearSigningKeys()
|
||||||
|
callback?.onFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearSigningKeys() {
|
||||||
|
masterPkSigning?.releaseSigning()
|
||||||
|
userPkSigning?.releaseSigning()
|
||||||
|
selfSigningPkSigning?.releaseSigning()
|
||||||
|
|
||||||
|
masterPkSigning = null
|
||||||
|
userPkSigning = null
|
||||||
|
selfSigningPkSigning = null
|
||||||
|
|
||||||
|
cryptoStore.setMyCrossSigningInfo(null)
|
||||||
|
cryptoStore.storePrivateKeysInfo(null, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetTrustOnKeyChange() {
|
||||||
|
Timber.i("## CrossSigning - Clear all other user trust")
|
||||||
|
cryptoStore.clearOtherUserTrust()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
||||||
|
* ┃ ALICE ┃ ┃ BOB ┃
|
||||||
|
* ┗━━━━━━━━┛ ┗━━━━━━━━┛
|
||||||
|
* MSK ┌────────────▶ MSK
|
||||||
|
* │
|
||||||
|
* │ │
|
||||||
|
* │ SSK │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* └──▶ USK ────────────┘
|
||||||
|
*/
|
||||||
|
override fun isUserTrusted(otherUserId: String): Boolean {
|
||||||
|
return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isCrossSigningVerified(): Boolean {
|
||||||
|
return checkSelfTrust().isVerified()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will not force a download of the key, but will verify signatures trust chain
|
||||||
|
*/
|
||||||
|
override fun checkUserTrust(otherUserId: String): UserTrustResult {
|
||||||
|
Timber.d("## CrossSigning checkUserTrust for $otherUserId")
|
||||||
|
if (otherUserId == userId) {
|
||||||
|
return checkSelfTrust()
|
||||||
|
}
|
||||||
|
// I trust a user if I trust his master key
|
||||||
|
// I can trust the master key if it is signed by my user key
|
||||||
|
// TODO what if the master key is signed by a device key that i have verified
|
||||||
|
|
||||||
|
// First let's get my user key
|
||||||
|
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
||||||
|
|
||||||
|
val myUserKey = myCrossSigningInfo?.userKey()
|
||||||
|
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
|
if (!myCrossSigningInfo.isTrusted()) {
|
||||||
|
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's get the other user master key
|
||||||
|
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
|
||||||
|
?: return UserTrustResult.UnknownCrossSignatureInfo(otherUserId)
|
||||||
|
|
||||||
|
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
|
||||||
|
?.get(userId) // Signatures made by me
|
||||||
|
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
|
||||||
|
|
||||||
|
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
|
||||||
|
Timber.d("## CrossSigning checkUserTrust false for $otherUserId, not signed by my UserSigningKey")
|
||||||
|
return UserTrustResult.KeyNotSigned(otherMasterKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that Alice USK signature of Bob MSK is valid
|
||||||
|
try {
|
||||||
|
olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable())
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserTrustResult.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkSelfTrust(): UserTrustResult {
|
||||||
|
// Special case when it's me,
|
||||||
|
// I have to check that MSK -> USK -> SSK
|
||||||
|
// and that MSK is trusted (i know the private key, or is signed by a trusted device)
|
||||||
|
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
||||||
|
|
||||||
|
val myMasterKey = myCrossSigningInfo?.masterKey()
|
||||||
|
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
|
// Is the master key trusted
|
||||||
|
// 1) check if I know the private key
|
||||||
|
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()
|
||||||
|
?.master
|
||||||
|
?.fromBase64NoPadding()
|
||||||
|
|
||||||
|
var isMaterKeyTrusted = false
|
||||||
|
if (masterPrivateKey != null) {
|
||||||
|
// Check if private match public
|
||||||
|
var olmPkSigning: OlmPkSigning? = null
|
||||||
|
try {
|
||||||
|
olmPkSigning = OlmPkSigning()
|
||||||
|
val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey)
|
||||||
|
isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
|
}
|
||||||
|
olmPkSigning?.releaseSigning()
|
||||||
|
} else {
|
||||||
|
// Maybe it's signed by a locally trusted device?
|
||||||
|
myMasterKey.signatures?.get(userId)?.forEach { (key, value) ->
|
||||||
|
val potentialDeviceId = key.withoutPrefix("ed25519:")
|
||||||
|
val potentialDevice = cryptoStore.getUserDevice(userId, potentialDeviceId)
|
||||||
|
if (potentialDevice != null && potentialDevice.isVerified) {
|
||||||
|
// Check signature validity?
|
||||||
|
try {
|
||||||
|
olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable())
|
||||||
|
isMaterKeyTrusted = true
|
||||||
|
return@forEach
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
// log
|
||||||
|
Timber.v(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMaterKeyTrusted) {
|
||||||
|
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
val myUserKey = myCrossSigningInfo.userKey()
|
||||||
|
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
|
val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures
|
||||||
|
?.get(userId) // Signatures made by me
|
||||||
|
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
|
||||||
|
|
||||||
|
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||||
|
Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK")
|
||||||
|
return UserTrustResult.KeyNotSigned(myUserKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that Alice USK signature of Alice MSK is valid
|
||||||
|
try {
|
||||||
|
olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable())
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mySSKey = myCrossSigningInfo.selfSigningKey()
|
||||||
|
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
|
val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures
|
||||||
|
?.get(userId) // Signatures made by me
|
||||||
|
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
|
||||||
|
|
||||||
|
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||||
|
Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK")
|
||||||
|
return UserTrustResult.KeyNotSigned(mySSKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that Alice USK signature of Alice MSK is valid
|
||||||
|
try {
|
||||||
|
olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable())
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserTrustResult.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||||
|
return cryptoStore.getCrossSigningInfo(otherUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||||
|
return cryptoStore.getLiveCrossSigningInfo(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||||
|
return cryptoStore.getMyCrossSigningInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canCrossSign(): Boolean {
|
||||||
|
return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
||||||
|
Timber.d("## CrossSigning - Mark user $userId as trusted ")
|
||||||
|
// We should have this user keys
|
||||||
|
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
|
||||||
|
if (otherMasterKeys == null) {
|
||||||
|
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val myKeys = getUserCrossSigningKeys(userId)
|
||||||
|
if (myKeys == null) {
|
||||||
|
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
|
||||||
|
if (userPubKey == null || userPkSigning == null) {
|
||||||
|
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the other MasterKey with our UserSigning key
|
||||||
|
val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java,
|
||||||
|
otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) }
|
||||||
|
|
||||||
|
if (newSignature == null) {
|
||||||
|
// race??
|
||||||
|
callback.onFailure(Throwable("## CrossSigning - Failed to sign"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoStore.setUserKeysAsTrusted(otherUserId, true)
|
||||||
|
// TODO update local copy with new signature directly here? kind of local echo of trust?
|
||||||
|
|
||||||
|
Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK")
|
||||||
|
val uploadQuery = UploadSignatureQueryBuilder()
|
||||||
|
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
||||||
|
.build()
|
||||||
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||||
|
this.callback = callback
|
||||||
|
}.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun signDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||||
|
// This device should be yours
|
||||||
|
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
|
if (device == null) {
|
||||||
|
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val myKeys = getUserCrossSigningKeys(userId)
|
||||||
|
if (myKeys == null) {
|
||||||
|
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
||||||
|
if (ssPubKey == null || selfSigningPkSigning == null) {
|
||||||
|
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign with self signing
|
||||||
|
val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable())
|
||||||
|
|
||||||
|
if (newSignature == null) {
|
||||||
|
// race??
|
||||||
|
callback.onFailure(Throwable("Failed to sign"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val toUpload = device.copy(
|
||||||
|
signatures = mapOf(
|
||||||
|
userId
|
||||||
|
to
|
||||||
|
mapOf(
|
||||||
|
"ed25519:$ssPubKey" to newSignature
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val uploadQuery = UploadSignatureQueryBuilder()
|
||||||
|
.withDeviceInfo(toUpload)
|
||||||
|
.build()
|
||||||
|
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||||
|
this.callback = callback
|
||||||
|
}.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||||
|
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
|
||||||
|
?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
|
||||||
|
|
||||||
|
val myKeys = getUserCrossSigningKeys(userId)
|
||||||
|
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
|
||||||
|
|
||||||
|
if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys))
|
||||||
|
|
||||||
|
val otherKeys = getUserCrossSigningKeys(otherUserId)
|
||||||
|
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId))
|
||||||
|
|
||||||
|
// TODO should we force verification ?
|
||||||
|
if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys))
|
||||||
|
|
||||||
|
// Check if the trust chain is valid
|
||||||
|
/*
|
||||||
|
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
||||||
|
* ┃ ALICE ┃ ┃ BOB ┃
|
||||||
|
* ┗━━━━━━━━┛ ┗━━━━━━━━┛
|
||||||
|
* MSK ┌────────────▶MSK
|
||||||
|
* │
|
||||||
|
* │ │ │
|
||||||
|
* │ SSK │ └──▶ SSK ──────────────────┐
|
||||||
|
* │ │ │
|
||||||
|
* │ │ USK │
|
||||||
|
* └──▶ USK ────────────┘ (not visible by │
|
||||||
|
* Alice) │
|
||||||
|
* ▼
|
||||||
|
* ┌──────────────┐
|
||||||
|
* │ BOB's Device │
|
||||||
|
* └──────────────┘
|
||||||
|
*/
|
||||||
|
|
||||||
|
val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
|
||||||
|
?: return legacyFallbackTrust(
|
||||||
|
locallyTrusted,
|
||||||
|
DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey()
|
||||||
|
?.unpaddedBase64PublicKey
|
||||||
|
?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check bob's device is signed by bob's SSK
|
||||||
|
try {
|
||||||
|
olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult {
|
||||||
|
return if (locallyTrusted == true) {
|
||||||
|
DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true))
|
||||||
|
} else {
|
||||||
|
crossSignTrustFail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUsersDeviceUpdate(users: List<String>) {
|
||||||
|
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
|
||||||
|
users.forEach { otherUserId ->
|
||||||
|
|
||||||
|
checkUserTrust(otherUserId).let {
|
||||||
|
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
||||||
|
setUserKeysAsTrusted(otherUserId, it.isVerified())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO if my keys have changes, i should recheck all devices of all users?
|
||||||
|
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
||||||
|
devices?.forEach { device ->
|
||||||
|
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||||
|
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||||
|
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherUserId == userId) {
|
||||||
|
// It's me, i should check if a newly trusted device is signing my master key
|
||||||
|
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||||
|
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||||
|
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||||
|
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||||
|
|
||||||
|
// If it's me, recheck trust of all users and devices?
|
||||||
|
val users = ArrayList<String>()
|
||||||
|
if (otherUserId == userId && currentTrust != trusted) {
|
||||||
|
cryptoStore.updateUsersTrust {
|
||||||
|
users.add(it)
|
||||||
|
checkUserTrust(it).isVerified()
|
||||||
|
}
|
||||||
|
|
||||||
|
users.forEach {
|
||||||
|
cryptoStore.getUserDeviceList(it)?.forEach { device ->
|
||||||
|
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||||
|
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||||
|
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||||
|
val allTrusted = userIds
|
||||||
|
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
|
||||||
|
|
||||||
|
val allUsersAreVerified = userIds.size == allTrusted.size
|
||||||
|
|
||||||
|
return if (allTrusted.isEmpty()) {
|
||||||
|
RoomEncryptionTrustLevel.Default
|
||||||
|
} else {
|
||||||
|
// If one of the verified user as an untrusted device -> warning
|
||||||
|
// Green if all devices of all verified users are trusted -> green
|
||||||
|
// else black
|
||||||
|
val allDevices = allTrusted.mapNotNull {
|
||||||
|
cryptoStore.getUserDeviceList(it)
|
||||||
|
}.flatten()
|
||||||
|
if (getMyCrossSigningKeys() != null) {
|
||||||
|
val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
|
||||||
|
if (hasWarning) {
|
||||||
|
RoomEncryptionTrustLevel.Warning
|
||||||
|
} else {
|
||||||
|
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val hasWarningLegacy = allDevices.any { !it.isVerified }
|
||||||
|
if (hasWarningLegacy) {
|
||||||
|
RoomEncryptionTrustLevel.Warning
|
||||||
|
} else {
|
||||||
|
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
data class DeviceTrustLevel(
|
||||||
|
val crossSigningVerified: Boolean,
|
||||||
|
val locallyVerified: Boolean?
|
||||||
|
) {
|
||||||
|
fun isVerified() = crossSigningVerified || locallyVerified == true
|
||||||
|
fun isCrossSigningVerified() = crossSigningVerified
|
||||||
|
fun isLocallyVerified() = locallyVerified
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
|
||||||
|
sealed class DeviceTrustResult {
|
||||||
|
data class Success(val level: DeviceTrustLevel) : DeviceTrustResult()
|
||||||
|
data class UnknownDevice(val deviceID: String) : DeviceTrustResult()
|
||||||
|
data class CrossSigningNotConfigured(val userID: String) : DeviceTrustResult()
|
||||||
|
data class KeysNotTrusted(val key: MXCrossSigningInfo) : DeviceTrustResult()
|
||||||
|
data class MissingDeviceSignature(val deviceId: String, val signingKey: String) : DeviceTrustResult()
|
||||||
|
data class InvalidDeviceSignature(val deviceId: String, val signingKey: String, val throwable: Throwable?) : DeviceTrustResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DeviceTrustResult.isSuccess() = this is DeviceTrustResult.Success
|
||||||
|
fun DeviceTrustResult.isCrossSignedVerified() = this is DeviceTrustResult.Success && level.isCrossSigningVerified()
|
||||||
|
fun DeviceTrustResult.isLocallyVerified() = this is DeviceTrustResult.Success && level.isLocallyVerified() == true
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
|
|
||||||
|
fun CryptoDeviceInfo.canonicalSignable(): String {
|
||||||
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CryptoCrossSigningKey.canonicalSignable(): String {
|
||||||
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toBase64NoPadding(): String {
|
||||||
|
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.fromBase64NoPadding(): ByteArray {
|
||||||
|
return Base64.decode(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
|
|
||||||
|
sealed class UserTrustResult {
|
||||||
|
object Success : UserTrustResult()
|
||||||
|
|
||||||
|
// data class Success(val deviceID: String, val crossSigned: Boolean) : UserTrustResult()
|
||||||
|
// data class UnknownDevice(val deviceID: String) : UserTrustResult()
|
||||||
|
data class CrossSigningNotConfigured(val userID: String) : UserTrustResult()
|
||||||
|
|
||||||
|
data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult()
|
||||||
|
data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult()
|
||||||
|
data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult()
|
||||||
|
data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun UserTrustResult.isVerified() = this is UserTrustResult.Success
|
|
@ -402,7 +402,7 @@ internal class KeysBackup @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceId != null) {
|
if (deviceId != null) {
|
||||||
val device = cryptoStore.getUserDevice(deviceId, userId)
|
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
var isSignatureValid = false
|
var isSignatureValid = false
|
||||||
|
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.keysbackup.model
|
package im.vector.matrix.android.internal.crypto.keysbackup.model
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A signature in a the `KeyBackupVersionTrust` object.
|
* A signature in a the `KeyBackupVersionTrust` object.
|
||||||
|
@ -26,7 +26,7 @@ class KeyBackupVersionTrustSignature {
|
||||||
/**
|
/**
|
||||||
* The device that signed the backup version.
|
* The device that signed the backup version.
|
||||||
*/
|
*/
|
||||||
var device: MXDeviceInfo? = null
|
var device: CryptoDeviceInfo? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*Flag to indicate the signature from this device is valid.
|
*Flag to indicate the signature from this device is valid.
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.keysbackup.model
|
package im.vector.matrix.android.internal.crypto.keysbackup.model
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A signature in a `KeysBackupVersionTrust` object.
|
* A signature in a `KeysBackupVersionTrust` object.
|
||||||
|
@ -32,7 +32,7 @@ class KeysBackupVersionTrustSignature {
|
||||||
* The device that signed the backup version.
|
* The device that signed the backup version.
|
||||||
* Can be null if the device is not known.
|
* Can be null if the device is not known.
|
||||||
*/
|
*/
|
||||||
var device: MXDeviceInfo? = null
|
var device: CryptoDeviceInfo? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to indicate the signature from this device is valid.
|
* Flag to indicate the signature from this device is valid.
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.RestKeyInfo
|
||||||
|
|
||||||
|
data class CryptoCrossSigningKey(
|
||||||
|
override val userId: String,
|
||||||
|
|
||||||
|
val usages: List<String>?,
|
||||||
|
|
||||||
|
override val keys: Map<String, String>,
|
||||||
|
|
||||||
|
override val signatures: Map<String, Map<String, String>>?,
|
||||||
|
|
||||||
|
var trustLevel: DeviceTrustLevel? = null
|
||||||
|
) : CryptoInfo {
|
||||||
|
|
||||||
|
override fun signalableJSONDictionary(): Map<String, Any> {
|
||||||
|
val map = HashMap<String, Any>()
|
||||||
|
userId.let { map["user_id"] = it }
|
||||||
|
usages?.let { map["usage"] = it }
|
||||||
|
keys.let { map["keys"] = it }
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
val unpaddedBase64PublicKey: String? = keys.values.firstOrNull()
|
||||||
|
|
||||||
|
val isMasterKey = usages?.contains(KeyUsage.MASTER.value) ?: false
|
||||||
|
val isSelfSigningKey = usages?.contains(KeyUsage.SELF_SIGNING.value) ?: false
|
||||||
|
val isUserKey = usages?.contains(KeyUsage.USER_SIGNING.value) ?: false
|
||||||
|
|
||||||
|
fun addSignatureAndCopy(userId: String, signedWithNoPrefix: String, signature: String): CryptoCrossSigningKey {
|
||||||
|
val updated = (signatures?.toMutableMap() ?: HashMap())
|
||||||
|
val userMap = updated[userId]?.toMutableMap()
|
||||||
|
?: HashMap<String, String>().also { updated[userId] = it }
|
||||||
|
userMap["ed25519:$signedWithNoPrefix"] = signature
|
||||||
|
|
||||||
|
return this.copy(
|
||||||
|
signatures = updated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyForSignature(userId: String, signedWithNoPrefix: String, signature: String): CryptoCrossSigningKey {
|
||||||
|
return this.copy(
|
||||||
|
signatures = mapOf(userId to mapOf("ed25519:$signedWithNoPrefix" to signature))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Builder(
|
||||||
|
val userId: String,
|
||||||
|
val usage: KeyUsage,
|
||||||
|
private var base64Pkey: String? = null,
|
||||||
|
private val signatures: ArrayList<Triple<String, String, String>> = ArrayList()
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun key(publicKeyBase64: String) = apply {
|
||||||
|
base64Pkey = publicKeyBase64
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signature(userId: String, keySignedBase64: String, base64Signature: String) = apply {
|
||||||
|
signatures.add(Triple(userId, keySignedBase64, base64Signature))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): CryptoCrossSigningKey {
|
||||||
|
val b64key = base64Pkey ?: throw IllegalArgumentException("")
|
||||||
|
|
||||||
|
val signMap = HashMap<String, HashMap<String, String>>()
|
||||||
|
signatures.forEach { info ->
|
||||||
|
val uMap = signMap[info.first]
|
||||||
|
?: HashMap<String, String>().also { signMap[info.first] = it }
|
||||||
|
uMap["ed25519:${info.second}"] = info.third
|
||||||
|
}
|
||||||
|
|
||||||
|
return CryptoCrossSigningKey(
|
||||||
|
userId = userId,
|
||||||
|
usages = listOf(usage.value),
|
||||||
|
keys = mapOf("ed25519:$b64key" to b64key),
|
||||||
|
signatures = signMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class KeyUsage(val value: String) {
|
||||||
|
MASTER("master"),
|
||||||
|
SELF_SIGNING("self_signing"),
|
||||||
|
USER_SIGNING("user_signing")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun CryptoCrossSigningKey.toRest(): RestKeyInfo {
|
||||||
|
return CryptoInfoMapper.map(this)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.RestDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMapper
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
|
||||||
|
|
||||||
|
data class CryptoDeviceInfo(
|
||||||
|
val deviceId: String,
|
||||||
|
override val userId: String,
|
||||||
|
var algorithms: List<String>? = null,
|
||||||
|
override val keys: Map<String, String>? = null,
|
||||||
|
override val signatures: Map<String, Map<String, String>>? = null,
|
||||||
|
val unsigned: JsonDict? = null,
|
||||||
|
|
||||||
|
// TODO how to store if this device is verified by a user SSK, or is legacy trusted?
|
||||||
|
// I need to know if it is trusted via cross signing (Trusted because bob verified it)
|
||||||
|
|
||||||
|
var trustLevel: DeviceTrustLevel? = null,
|
||||||
|
var isBlocked: Boolean = false
|
||||||
|
) : CryptoInfo {
|
||||||
|
|
||||||
|
val isVerified: Boolean
|
||||||
|
get() = trustLevel?.isVerified() ?: false
|
||||||
|
|
||||||
|
val isUnknown: Boolean
|
||||||
|
get() = trustLevel == null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the fingerprint
|
||||||
|
*/
|
||||||
|
fun fingerprint(): String? {
|
||||||
|
return keys
|
||||||
|
?.takeIf { !deviceId.isBlank() }
|
||||||
|
?.get("ed25519:$deviceId")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the identity key
|
||||||
|
*/
|
||||||
|
fun identityKey(): String? {
|
||||||
|
return keys
|
||||||
|
?.takeIf { !deviceId.isBlank() }
|
||||||
|
?.get("curve25519:$deviceId")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the display name
|
||||||
|
*/
|
||||||
|
fun displayName(): String? {
|
||||||
|
return unsigned?.get("device_display_name") as? String
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun signalableJSONDictionary(): Map<String, Any> {
|
||||||
|
val map = HashMap<String, Any>()
|
||||||
|
map["device_id"] = deviceId
|
||||||
|
map["user_id"] = userId
|
||||||
|
algorithms?.let { map["algorithms"] = it }
|
||||||
|
keys?.let { map["keys"] = it }
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @return a dictionary of the parameters
|
||||||
|
// */
|
||||||
|
// fun toDeviceKeys(): DeviceKeys {
|
||||||
|
// return DeviceKeys(
|
||||||
|
// userId = userId,
|
||||||
|
// deviceId = deviceId,
|
||||||
|
// algorithms = algorithms!!,
|
||||||
|
// keys = keys!!,
|
||||||
|
// signatures = signatures!!
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun CryptoDeviceInfo.toRest(): RestDeviceInfo {
|
||||||
|
return CryptoInfoMapper.map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity {
|
||||||
|
return CryptoMapper.mapToEntity(this)
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic crypto info.
|
||||||
|
* Can be a device (CryptoDeviceInfo), as well as a CryptoCrossSigningInfo (can be seen as a kind of virtual device)
|
||||||
|
*/
|
||||||
|
interface CryptoInfo {
|
||||||
|
|
||||||
|
val userId: String
|
||||||
|
|
||||||
|
val keys: Map<String, String>?
|
||||||
|
|
||||||
|
val signatures: Map<String, Map<String, String>>?
|
||||||
|
|
||||||
|
fun signalableJSONDictionary(): Map<String, Any>
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.RestDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.RestKeyInfo
|
||||||
|
|
||||||
|
internal object CryptoInfoMapper {
|
||||||
|
|
||||||
|
fun map(restDeviceInfo: RestDeviceInfo): CryptoDeviceInfo {
|
||||||
|
return CryptoDeviceInfo(
|
||||||
|
deviceId = restDeviceInfo.deviceId,
|
||||||
|
userId = restDeviceInfo.userId,
|
||||||
|
algorithms = restDeviceInfo.algorithms,
|
||||||
|
keys = restDeviceInfo.keys,
|
||||||
|
signatures = restDeviceInfo.signatures,
|
||||||
|
unsigned = restDeviceInfo.unsigned,
|
||||||
|
trustLevel = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(cryptoDeviceInfo: CryptoDeviceInfo): RestDeviceInfo {
|
||||||
|
return RestDeviceInfo(
|
||||||
|
deviceId = cryptoDeviceInfo.deviceId,
|
||||||
|
algorithms = cryptoDeviceInfo.algorithms,
|
||||||
|
keys = cryptoDeviceInfo.keys,
|
||||||
|
signatures = cryptoDeviceInfo.signatures,
|
||||||
|
unsigned = cryptoDeviceInfo.unsigned,
|
||||||
|
userId = cryptoDeviceInfo.userId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(keyInfo: RestKeyInfo): CryptoCrossSigningKey {
|
||||||
|
return CryptoCrossSigningKey(
|
||||||
|
userId = keyInfo.userId,
|
||||||
|
usages = keyInfo.usages,
|
||||||
|
keys = keyInfo.keys ?: emptyMap(),
|
||||||
|
signatures = keyInfo.signatures,
|
||||||
|
trustLevel = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(keyInfo: CryptoCrossSigningKey): RestKeyInfo {
|
||||||
|
return RestKeyInfo(
|
||||||
|
userId = keyInfo.userId,
|
||||||
|
usages = keyInfo.usages,
|
||||||
|
keys = keyInfo.keys,
|
||||||
|
signatures = keyInfo.signatures
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RestDeviceInfo.toCryptoModel(): CryptoDeviceInfo {
|
||||||
|
return map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CryptoDeviceInfo.toRest(): RestDeviceInfo {
|
||||||
|
return map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun RestKeyInfo.toCryptoModel(): CryptoCrossSigningKey {
|
||||||
|
// return map(this)
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun CryptoCrossSigningKey.toRest(): RestKeyInfo {
|
||||||
|
return map(this)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ data class MXOlmSessionResult(
|
||||||
/**
|
/**
|
||||||
* the device
|
* the device
|
||||||
*/
|
*/
|
||||||
val deviceInfo: MXDeviceInfo,
|
val deviceInfo: CryptoDeviceInfo,
|
||||||
/**
|
/**
|
||||||
* Base64 olm session id.
|
* Base64 olm session id.
|
||||||
* null if no session could be established.
|
* null if no session could be established.
|
||||||
|
|
|
@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class DeleteDeviceParams(
|
internal data class DeleteDeviceParams(
|
||||||
@Json(name = "auth")
|
@Json(name = "auth")
|
||||||
var deleteDeviceAuth: DeleteDeviceAuth? = null
|
val userPasswordAuth: UserPasswordAuth? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,22 +18,24 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DeviceKeys(
|
data class DeviceKeys(
|
||||||
@Json(name = "user_id")
|
@Json(name = "user_id")
|
||||||
val userId: String,
|
val userId: String?,
|
||||||
|
|
||||||
@Json(name = "device_id")
|
@Json(name = "device_id")
|
||||||
val deviceId: String,
|
val deviceId: String?,
|
||||||
|
|
||||||
@Json(name = "algorithms")
|
@Json(name = "algorithms")
|
||||||
val algorithms: List<String>,
|
val algorithms: List<String>?,
|
||||||
|
|
||||||
@Json(name = "keys")
|
@Json(name = "keys")
|
||||||
val keys: Map<String, String>,
|
val keys: Map<String, String>?,
|
||||||
|
|
||||||
@Json(name = "signatures")
|
@Json(name = "signatures")
|
||||||
val signatures: JsonDict
|
val signatures: Map<String, Map<String, String>>?,
|
||||||
|
|
||||||
|
@Json(name = "usage")
|
||||||
|
val usage: List<String>? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* This class describes the key changes response
|
* This class describes the key changes response
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyChangesResponse(
|
internal data class KeyChangesResponse(
|
||||||
// list of user ids which have new devices
|
// list of user ids which have new devices
|
||||||
@Json(name = "changed")
|
@Json(name = "changed")
|
||||||
var changed: List<String>? = null,
|
var changed: List<String>? = null,
|
||||||
|
|
|
@ -17,13 +17,15 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationAccept(
|
internal data class KeyVerificationAccept(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* string to identify the transaction.
|
* string to identify the transaction.
|
||||||
|
@ -31,39 +33,41 @@ data class KeyVerificationAccept(
|
||||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id")
|
||||||
var transactionID: String? = null,
|
override val transactionID: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||||
*/
|
*/
|
||||||
@Json(name = "key_agreement_protocol")
|
@Json(name = "key_agreement_protocol")
|
||||||
var keyAgreementProtocol: String? = null,
|
override val keyAgreementProtocol: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
* The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||||
*/
|
*/
|
||||||
var hash: String? = null,
|
@Json(name = "hash")
|
||||||
|
override val hash: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
* The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||||
*/
|
*/
|
||||||
@Json(name = "message_authentication_code")
|
@Json(name = "message_authentication_code")
|
||||||
var messageAuthenticationCode: String? = null,
|
override val messageAuthenticationCode: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device
|
* An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device
|
||||||
*/
|
*/
|
||||||
@Json(name = "short_authentication_string")
|
@Json(name = "short_authentication_string")
|
||||||
var shortAuthenticationStrings: List<String>? = null,
|
override val shortAuthenticationStrings: List<String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64)
|
* The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64)
|
||||||
* and the canonical JSON representation of the m.key.verification.start message.
|
* and the canonical JSON representation of the m.key.verification.start message.
|
||||||
*/
|
*/
|
||||||
var commitment: String? = null
|
@Json(name = "commitment")
|
||||||
) : SendToDeviceObject {
|
override var commitment: String? = null
|
||||||
|
) : SendToDeviceObject, VerificationInfoAccept {
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank()
|
if (transactionID.isNullOrBlank()
|
||||||
|| keyAgreementProtocol.isNullOrBlank()
|
|| keyAgreementProtocol.isNullOrBlank()
|
||||||
|| hash.isNullOrBlank()
|
|| hash.isNullOrBlank()
|
||||||
|
@ -76,21 +80,23 @@ data class KeyVerificationAccept(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun toSendToDeviceObject() = this
|
||||||
fun create(tid: String,
|
|
||||||
keyAgreementProtocol: String,
|
companion object : VerificationInfoAcceptFactory {
|
||||||
hash: String,
|
override fun create(tid: String,
|
||||||
commitment: String,
|
keyAgreementProtocol: String,
|
||||||
messageAuthenticationCode: String,
|
hash: String,
|
||||||
shortAuthenticationStrings: List<String>): KeyVerificationAccept {
|
commitment: String,
|
||||||
return KeyVerificationAccept().apply {
|
messageAuthenticationCode: String,
|
||||||
this.transactionID = tid
|
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
|
||||||
this.keyAgreementProtocol = keyAgreementProtocol
|
return KeyVerificationAccept(
|
||||||
this.hash = hash
|
transactionID = tid,
|
||||||
this.commitment = commitment
|
keyAgreementProtocol = keyAgreementProtocol,
|
||||||
this.messageAuthenticationCode = messageAuthenticationCode
|
hash = hash,
|
||||||
this.shortAuthenticationStrings = shortAuthenticationStrings
|
commitment = commitment,
|
||||||
}
|
messageAuthenticationCode = messageAuthenticationCode,
|
||||||
|
shortAuthenticationStrings = shortAuthenticationStrings
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,40 +18,43 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoCancel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To device event sent by either party to cancel a key verification.
|
* To device event sent by either party to cancel a key verification.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationCancel(
|
internal data class KeyVerificationCancel(
|
||||||
/**
|
/**
|
||||||
* the transaction ID of the verification to cancel
|
* the transaction ID of the verification to cancel
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id")
|
||||||
var transactionID: String? = null,
|
override val transactionID: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* machine-readable reason for cancelling, see #CancelCode
|
* machine-readable reason for cancelling, see #CancelCode
|
||||||
*/
|
*/
|
||||||
var code: String? = null,
|
override val code: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
||||||
*/
|
*/
|
||||||
var reason: String? = null
|
override val reason: String? = null
|
||||||
) : SendToDeviceObject {
|
) : SendToDeviceObject, VerificationInfoCancel {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel {
|
fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel {
|
||||||
return KeyVerificationCancel().apply {
|
return KeyVerificationCancel(
|
||||||
this.transactionID = tid
|
tid,
|
||||||
this.code = cancelCode.value
|
cancelCode.value,
|
||||||
this.reason = cancelCode.humanReadable
|
cancelCode.humanReadable
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.crypto.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDone
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a key verification with another user's devices.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class KeyVerificationDone(
|
||||||
|
@Json(name = "transaction_id") override var transactionID: String? = null
|
||||||
|
) : SendToDeviceObject, VerificationInfoDone {
|
||||||
|
|
||||||
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,37 +17,35 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationKey(
|
internal data class KeyVerificationKey(
|
||||||
/**
|
/**
|
||||||
* the ID of the transaction that the message is part of
|
* the ID of the transaction that the message is part of
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||||
@JvmField
|
|
||||||
var transactionID: String? = null,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The device’s ephemeral public key, as an unpadded base64 string
|
* The device’s ephemeral public key, as an unpadded base64 string
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@Json(name = "key") override val key: String? = null
|
||||||
var key: String? = null
|
|
||||||
|
|
||||||
) : SendToDeviceObject {
|
) : SendToDeviceObject, VerificationInfoKey {
|
||||||
|
|
||||||
companion object {
|
companion object : VerificationInfoKeyFactory {
|
||||||
fun create(tid: String, key: String): KeyVerificationKey {
|
override fun create(tid: String, pubKey: String): KeyVerificationKey {
|
||||||
return KeyVerificationKey().apply {
|
return KeyVerificationKey(tid, pubKey)
|
||||||
this.transactionID = tid
|
|
||||||
this.key = key
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,49 +17,32 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMacFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by both devices to send the MAC of their device key to the other device.
|
* Sent by both devices to send the MAC of their device key to the other device.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationMac(
|
internal data class KeyVerificationMac(
|
||||||
/**
|
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||||
* the ID of the transaction that the message is part of
|
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
||||||
*/
|
@Json(name = "keys") override val keys: String? = null
|
||||||
@Json(name = "transaction_id")
|
|
||||||
var transactionID: String? = null,
|
|
||||||
|
|
||||||
/**
|
) : SendToDeviceObject, VerificationInfoMac {
|
||||||
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
|
|
||||||
*/
|
|
||||||
@JvmField
|
|
||||||
var mac: Map<String, String>? = null,
|
|
||||||
|
|
||||||
/**
|
override fun isValid(): Boolean {
|
||||||
* The MAC of the comma-separated, sorted list of key IDs given in the mac property,
|
|
||||||
* as an unpadded base64 string, calculated using the MAC key.
|
|
||||||
* For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will
|
|
||||||
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
|
|
||||||
*/
|
|
||||||
@JvmField
|
|
||||||
var keys: String? = null
|
|
||||||
|
|
||||||
) : SendToDeviceObject {
|
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
|
||||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
||||||
fun create(tid: String, mac: Map<String, String>, keys: String): KeyVerificationMac {
|
|
||||||
return KeyVerificationMac().apply {
|
companion object : VerificationInfoMacFactory {
|
||||||
this.transactionID = tid
|
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
|
||||||
this.mac = mac
|
return KeyVerificationMac(tid, mac, keys)
|
||||||
this.keys = keys
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a key verification with another user's devices.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class KeyVerificationReady(
|
||||||
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
|
@Json(name = "methods") override val methods: List<String>?,
|
||||||
|
@Json(name = "transaction_id") override val transactionID: String? = null
|
||||||
|
) : SendToDeviceObject, VerificationInfoReady {
|
||||||
|
|
||||||
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
return !transactionID.isNullOrBlank() && !fromDevice.isNullOrBlank() && !methods.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a key verification with another user's devices.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class KeyVerificationRequest(
|
||||||
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
|
@Json(name = "methods") override val methods: List<String>,
|
||||||
|
@Json(name = "timestamp") override val timestamp: Long?,
|
||||||
|
@Json(name = "transaction_id") override var transactionID: String? = null
|
||||||
|
|
||||||
|
) : SendToDeviceObject, VerificationInfoRequest {
|
||||||
|
|
||||||
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,81 +18,64 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||||
import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction
|
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||||
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by Alice to initiate an interactive key verification.
|
* Sent by Alice to initiate an interactive key verification.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
class KeyVerificationStart : SendToDeviceObject {
|
internal data class KeyVerificationStart(
|
||||||
|
@Json(name = "from_device") override val fromDevice: String? = null,
|
||||||
|
override val method: String? = null,
|
||||||
|
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||||
|
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
|
||||||
|
@Json(name = "hashes") override val hashes: List<String>? = null,
|
||||||
|
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>? = null,
|
||||||
|
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>? = null,
|
||||||
|
// For QR code verification
|
||||||
|
@Json(name = "secret") override val sharedSecret: String? = null
|
||||||
|
) : SendToDeviceObject, VerificationInfoStart {
|
||||||
|
|
||||||
/**
|
override fun toCanonicalJson(): String? {
|
||||||
* Alice’s device ID
|
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
|
||||||
*/
|
|
||||||
@Json(name = "from_device")
|
|
||||||
var fromDevice: String? = null
|
|
||||||
|
|
||||||
var method: String? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String to identify the transaction.
|
|
||||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
|
|
||||||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
|
||||||
*/
|
|
||||||
@Json(name = "transaction_id")
|
|
||||||
var transactionID: String? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of key agreement protocols that Alice’s client understands.
|
|
||||||
* Must include “curve25519”.
|
|
||||||
* Other methods may be defined in the future
|
|
||||||
*/
|
|
||||||
@Json(name = "key_agreement_protocols")
|
|
||||||
var keyAgreementProtocols: List<String>? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of hashes that Alice’s client understands.
|
|
||||||
* Must include “sha256”. Other methods may be defined in the future.
|
|
||||||
*/
|
|
||||||
var hashes: List<String>? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of message authentication codes that Alice’s client understands.
|
|
||||||
* Must include “hkdf-hmac-sha256”.
|
|
||||||
* Other methods may be defined in the future.
|
|
||||||
*/
|
|
||||||
@Json(name = "message_authentication_codes")
|
|
||||||
var messageAuthenticationCodes: List<String>? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of short authentication string methods that Alice’s client (and Alice) understands.
|
|
||||||
* Must include “decimal”.
|
|
||||||
* This document also describes the “emoji” method.
|
|
||||||
* Other methods may be defined in the future
|
|
||||||
*/
|
|
||||||
@Json(name = "short_authentication_string")
|
|
||||||
var shortAuthenticationStrings: List<String>? = null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val VERIF_METHOD_SAS = "m.sas.v1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
// TODO Move those method to the interface?
|
||||||
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank()
|
if (transactionID.isNullOrBlank()
|
||||||
|| fromDevice.isNullOrBlank()
|
|| fromDevice.isNullOrBlank()
|
||||||
|| method != VERIF_METHOD_SAS
|
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
||||||
|| keyAgreementProtocols.isNullOrEmpty()
|
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
||||||
|| hashes.isNullOrEmpty()
|
|
||||||
|| hashes?.contains("sha256") == false
|
|
||||||
|| messageAuthenticationCodes.isNullOrEmpty()
|
|
||||||
|| (messageAuthenticationCodes?.contains(SASVerificationTransaction.SAS_MAC_SHA256) == false
|
|
||||||
&& messageAuthenticationCodes?.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF) == false)
|
|
||||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
|
||||||
|| shortAuthenticationStrings?.contains(SasMode.DECIMAL) == false) {
|
|
||||||
Timber.e("## received invalid verification request")
|
Timber.e("## received invalid verification request")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isValidSas(): Boolean {
|
||||||
|
if (keyAgreementProtocols.isNullOrEmpty()
|
||||||
|
|| hashes.isNullOrEmpty()
|
||||||
|
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||||
|
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||||
|
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||||
|
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||||
|
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidReciprocate(): Boolean {
|
||||||
|
if (sharedSecret.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toSendToDeviceObject() = this
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices.
|
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeysClaimBody(
|
internal data class KeysClaimBody(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
|
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
|
||||||
|
|
|
@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices.
|
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeysClaimResponse(
|
internal data class KeysClaimResponse(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The requested keys ordered by device by user.
|
* The requested keys ordered by device by user.
|
||||||
|
|
|
@ -24,7 +24,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* This class represents the body to /keys/query
|
* This class represents the body to /keys/query
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeysQueryBody(
|
internal data class KeysQueryBody(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
|
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2016 OpenMarket Ltd
|
|
||||||
* Copyright 2017 Vector Creations 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.
|
||||||
|
@ -19,24 +18,37 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the response to /keys/query request made by downloadKeysForUsers
|
* This class represents the response to /keys/query request made by downloadKeysForUsers
|
||||||
|
*
|
||||||
|
* After uploading cross-signing keys, they will be included under the /keys/query endpoint under the master_keys,
|
||||||
|
* self_signing_keys and user_signing_keys properties.
|
||||||
|
*
|
||||||
|
* The user_signing_keys property will only be included when a user requests their own keys.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeysQueryResponse(
|
internal data class KeysQueryResponse(
|
||||||
/**
|
/**
|
||||||
* The device keys per devices per users.
|
* The device keys per devices per users.
|
||||||
* Map from userId to map from deviceId to MXDeviceInfo
|
* Map from userId to map from deviceId to MXDeviceInfo
|
||||||
* TODO Use MXUsersDevicesMap?
|
* TODO Use MXUsersDevicesMap?
|
||||||
*/
|
*/
|
||||||
@Json(name = "device_keys")
|
@Json(name = "device_keys")
|
||||||
var deviceKeys: Map<String, Map<String, MXDeviceInfo>>? = null,
|
val deviceKeys: Map<String, Map<String, RestDeviceInfo>>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The failures sorted by homeservers. TODO Bad comment ?
|
* The failures sorted by homeservers. TODO Bad comment ?
|
||||||
* TODO Use MXUsersDevicesMap?
|
* TODO Use MXUsersDevicesMap?
|
||||||
*/
|
*/
|
||||||
var failures: Map<String, Map<String, Any>>? = null
|
val failures: Map<String, Map<String, Any>>? = null,
|
||||||
|
|
||||||
|
@Json(name = "master_keys")
|
||||||
|
val masterKeys: Map<String, RestKeyInfo?>? = null,
|
||||||
|
|
||||||
|
@Json(name = "self_signing_keys")
|
||||||
|
val selfSigningKeys: Map<String, RestKeyInfo?>? = null,
|
||||||
|
|
||||||
|
@Json(name = "user_signing_keys")
|
||||||
|
val userSigningKeys: Map<String, RestKeyInfo?>? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,10 +21,10 @@ import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeysUploadBody(
|
internal data class KeysUploadBody(
|
||||||
@Json(name = "device_keys")
|
@Json(name = "device_keys")
|
||||||
var deviceKeys: DeviceKeys? = null,
|
val deviceKeys: RestDeviceInfo? = null,
|
||||||
|
|
||||||
@Json(name = "one_time_keys")
|
@Json(name = "one_time_keys")
|
||||||
var oneTimeKeys: JsonDict? = null
|
val oneTimeKeys: JsonDict? = null
|
||||||
)
|
)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue