Merge pull request #770 from vector-im/cross_signing

Cross signing
This commit is contained in:
Benoit Marty 2020-01-31 17:05:18 +01:00 committed by GitHub
commit fd3619b100
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
357 changed files with 16657 additions and 3670 deletions

2
.gitignore vendored
View file

@ -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

View file

@ -3,7 +3,7 @@ Changes in RiotX 0.14.0 (2020-XX-XX)
Features ✨: Features ✨:
- 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)
@ -120,6 +120,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

View file

@ -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,6 +30,7 @@ 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
@ -98,6 +100,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 {

View file

@ -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()
} }

View file

@ -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())

View file

@ -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)
} }
/** /**
@ -272,7 +283,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)
} }
} }

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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,17 +429,17 @@ 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
@ -412,14 +448,14 @@ class SASTest : InstrumentedTest {
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()
@ -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,20 +493,20 @@ 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
@ -479,14 +515,14 @@ class SASTest : InstrumentedTest {
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()
@ -503,17 +539,17 @@ 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
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)

View file

@ -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
}
}

View file

@ -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" />

View file

@ -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
}

View file

@ -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

View file

@ -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 = " ")

View file

@ -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)
}

View file

@ -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", "/")
}
} }

View file

@ -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,11 @@ 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(userId: String): LiveData<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener) fun addNewSessionListener(newSessionListener: NewSessionListener)

View file

@ -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,

View file

@ -0,0 +1,65 @@
/*
* 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.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
}

View file

@ -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 }
}

View file

@ -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 {

View file

@ -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()

View file

@ -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 {

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -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()
} }

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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.

View file

@ -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"

View file

@ -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
) )

View file

@ -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?
} }

View file

@ -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
) )

View file

@ -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.
)

View file

@ -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>
)

View file

@ -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

View file

@ -35,15 +35,15 @@ 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 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. * A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included. * 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"] * NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/ */
var visibility: RoomDirectoryVisibility? = null @Json(name = "visibility")
val visibility: RoomDirectoryVisibility? = null,
/** /**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room. * The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
@ -51,32 +51,34 @@ class CreateRoomParams {
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com. * 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 = "room_alias_name") @Json(name = "room_alias_name")
var roomAliasName: String? = null val 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.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name. * See Room Events for more information on m.room.name.
*/ */
var name: String? = null @Json(name = "name")
val name: String? = null,
/** /**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for 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.topic. * See Room Events for more information on m.room.topic.
*/ */
var topic: String? = null @Json(name = "topic")
val topic: String? = null,
/** /**
* A list of user IDs to invite to the room. * A list of user IDs to invite to the room.
* This will tell the server to invite everyone in the list to the newly created room. * This will tell the server to invite everyone in the list to the newly created room.
*/ */
@Json(name = "invite") @Json(name = "invite")
var invitedUserIds: MutableList<String>? = null val invitedUserIds: List<String>? = null,
/** /**
* A list of objects representing third party IDs to invite into the room. * A list of objects representing third party IDs to invite into the room.
*/ */
@Json(name = "invite_3pid") @Json(name = "invite_3pid")
var invite3pids: MutableList<Invite3Pid>? = null val invite3pids: List<Invite3Pid>? = null,
/** /**
* Extra keys to be added to the content of the m.room.create. * Extra keys to be added to the content of the m.room.create.
@ -84,7 +86,7 @@ class CreateRoomParams {
* Future versions of the specification may allow the server to clobber other keys. * Future versions of the specification may allow the server to clobber other keys.
*/ */
@Json(name = "creation_content") @Json(name = "creation_content")
var creationContent: Any? = null val creationContent: Any? = null,
/** /**
* A list of state events to set in the new room. * A list of state events to set in the new room.
@ -93,7 +95,7 @@ class CreateRoomParams {
* Takes precedence over events set by presets, but gets overridden by name and topic keys. * Takes precedence over events set by presets, but gets overridden by name and topic keys.
*/ */
@Json(name = "initial_state") @Json(name = "initial_state")
var initialStates: MutableList<Event>? = null val initialStates: List<Event>? = null,
/** /**
* Convenience parameter for setting various default state events based on a preset. Must be either: * Convenience parameter for setting various default state events based on a preset. Must be either:
@ -102,28 +104,42 @@ class CreateRoomParams {
* room creator. * room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared. * public_chat: => join_rules is set to public. history_visibility is set to shared.
*/ */
var preset: CreateRoomPreset? = null @Json(name = "preset")
val preset: CreateRoomPreset? = 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. * 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.
* See Direct Messaging for more information. * See Direct Messaging for more information.
*/ */
@Json(name = "is_direct") @Json(name = "is_direct")
var isDirect: Boolean? = null val isDirect: Boolean? = null,
/** /**
* The power level content to override in the default power level event * The power level content to override in the default power level event
*/ */
@Json(name = "power_level_content_override") @Json(name = "power_level_content_override")
var powerLevelContentOverride: PowerLevelsContent? = null val powerLevelContentOverride: PowerLevelsContent? = null
) {
/**
* Set to true means that if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
*/
@Transient
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
private set
fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams {
enableEncryptionIfInvitedUsersSupportIt = true
return this
}
/** /**
* Add the crypto algorithm to the room creation parameters. * Add the crypto algorithm to the room creation parameters.
* *
* @param algorithm the algorithm * @param 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 { } else {
initialStates!!.add(historyVisibilityEvent) 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(
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
isDirect = true 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(
idServer = hsConfig.identityServerUri!!.host!!,
medium = ThreePidMedium.EMAIL, medium = ThreePidMedium.EMAIL,
address = id) address = id
)
invite3pids!!.add(pid) }
} else if (isUserId(id)) { .orEmpty())
.distinct(),
invitedUserIds = (invitedUserIds.orEmpty() + ids
.filter { id -> isUserId(id) }
// do not invite oneself // do not invite oneself
if (userId != id) { .filter { id -> id != userId })
if (null == invitedUserIds) { .distinct()
invitedUserIds = ArrayList() )
}
invitedUserIds!!.add(id)
}
}
// TODO add phonenumbers when it will be available // TODO add phonenumbers when it will be available
} }
} }
}

View file

@ -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?
)

View file

@ -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

View file

@ -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
)
}
}
}

View file

@ -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
)
)
}
}
}

View file

@ -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()
}

View file

@ -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 devices 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
)
)
}
}
}

View file

@ -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
)
)
}
}
}

View file

@ -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
)
)
}
}
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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"

View file

@ -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
} }

View file

@ -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
@ -122,8 +127,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,
// //
@ -149,6 +156,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.
@ -175,7 +186,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)
} }
@ -200,7 +221,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
} }
@ -320,9 +341,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
@ -356,7 +379,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
@ -369,13 +392,20 @@ 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(userId: String): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList(userId)
}
/** /**
* Set the devices as known * Set the devices as known
@ -400,7 +430,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
} }
} }
@ -417,12 +447,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)
} }
/** /**
@ -503,9 +533,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 {
@ -767,11 +796,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)
} }
@ -1011,8 +1044,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 ->
@ -1027,7 +1060,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)

View file

@ -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,7 +198,7 @@ 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"]) {
@ -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,62 @@ 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) { ?.toMutableMap()
val mutableDevices = devices.toMutableMap()
for ((deviceId, deviceInfo) in devices) { Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
if (!models.isNullOrEmpty()) {
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) models.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 models[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 models[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, models)
} }
// 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 +386,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 +414,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")

View file

@ -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)
} }

View file

@ -141,6 +141,7 @@ internal class MXOlmDevice @Inject constructor(
*/ */
fun release() { fun release() {
olmAccount?.releaseAccount() olmAccount?.releaseAccount()
olmUtility?.releaseUtility()
} }
/** /**

View file

@ -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)

View file

@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.internal.crypto.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

View file

@ -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)

View file

@ -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()

View file

@ -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)
}
} }
} }

View file

@ -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,15 @@ 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
val requestOnFail =
if (cryptoStore.getMyCrossSigningInfo() != null) {
cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
} else {
// Legacy
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 +310,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 {

View file

@ -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

View file

@ -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) {

View file

@ -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) {

View file

@ -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

View file

@ -0,0 +1,658 @@
/*
* 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.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.TaskConstraints
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.constraints = TaskConstraints(true)
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.constraints = TaskConstraints(true)
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())
}
}
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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) {

View file

@ -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.

View file

@ -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.

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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>
}

View file

@ -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)
}
}

View file

@ -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.

View file

@ -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
) )

View file

@ -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
) )

View file

@ -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,

View file

@ -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(
* Alices device should record this ID and use it in future messages in this transaction. * Alices 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 Bobs device has selected to use, out of the list proposed by Alices device * The key agreement protocol that Bobs device has selected to use, out of the list proposed by Alices device
*/ */
@Json(name = "key_agreement_protocol") @Json(name = "key_agreement_protocol")
var keyAgreementProtocol: String? = null, override val keyAgreementProtocol: String? = null,
/** /**
* The hash algorithm that Bobs device has selected to use, out of the list proposed by Alices device * The hash algorithm that Bobs device has selected to use, out of the list proposed by Alices device
*/ */
var hash: String? = null, @Json(name = "hash")
override val hash: String? = null,
/** /**
* The message authentication code that Bobs device has selected to use, out of the list proposed by Alices device * The message authentication code that Bobs device has selected to use, out of the list proposed by Alices 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 Bobs client (and Bob) understands. Must be a subset of the list proposed by Alices device * An array of short authentication string methods that Bobs client (and Bob) understands. Must be a subset of the list proposed by Alices 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 devices ephemeral public key (QB, encoded as unpadded base64) * The hash (encoded as unpadded base64) of the concatenation of the devices 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,
companion object : VerificationInfoAcceptFactory {
override fun create(tid: String,
keyAgreementProtocol: String, keyAgreementProtocol: String,
hash: String, hash: String,
commitment: String, commitment: String,
messageAuthenticationCode: String, messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>): KeyVerificationAccept { shortAuthenticationStrings: List<String>): VerificationInfoAccept {
return KeyVerificationAccept().apply { return KeyVerificationAccept(
this.transactionID = tid transactionID = tid,
this.keyAgreementProtocol = keyAgreementProtocol keyAgreementProtocol = keyAgreementProtocol,
this.hash = hash hash = hash,
this.commitment = commitment commitment = commitment,
this.messageAuthenticationCode = messageAuthenticationCode messageAuthenticationCode = messageAuthenticationCode,
this.shortAuthenticationStrings = shortAuthenticationStrings shortAuthenticationStrings = shortAuthenticationStrings
} )
} }
} }
} }

View file

@ -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
} }

View file

@ -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 devices ephemeral public key, as an unpadded base64 string * The devices 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
} }

View file

@ -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
}
} }
} }
} }

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -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? {
* Alices 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.
* Alices 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 Alices 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 Alices 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 Alices 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 Alices 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
} }

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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
) )

View file

@ -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
) )

View file

@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
* This class represents the response to /keys/upload request made by uploadKeys. * This class represents the response to /keys/upload request made by uploadKeys.
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class KeysUploadResponse( internal data class KeysUploadResponse(
/** /**
* The count per algorithm as returned by the home server: a map (algorithm to count). * The count per algorithm as returned by the home server: a map (algorithm to count).

View file

@ -0,0 +1,60 @@
/*
* 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.api.util.JsonDict
@JsonClass(generateAdapter = true)
internal data class RestDeviceInfo(
/**
* The id of this device.
*/
@Json(name = "device_id")
val deviceId: String,
/**
* the user id
*/
@Json(name = "user_id")
val userId: String,
/**
* The list of algorithms supported by this device.
*/
@Json(name = "algorithms")
val algorithms: List<String>? = null,
/**
* A map from "<key type>:<deviceId>" to "<base64-encoded key>".
*/
@Json(name = "keys")
val keys: Map<String, String>? = null,
/**
* The signature of this MXDeviceInfo.
* A map from "<userId>" to a map from "<key type>:<deviceId>" to "<signature>"
*/
@Json(name = "signatures")
val signatures: Map<String, Map<String, String>>? = null,
/*
* Additional data from the home server.
*/
@Json(name = "unsigned")
val unsigned: JsonDict? = null
)

View file

@ -0,0 +1,57 @@
/*
* 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.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoInfoMapper
@JsonClass(generateAdapter = true)
internal data class RestKeyInfo(
/**
* The user who owns the key
*/
@Json(name = "user_id")
val userId: String,
/**
* Allowed uses for the key.
* Must contain "master" for master keys, "self_signing" for self-signing keys, and "user_signing" for user-signing keys.
* See CrossSigningKeyInfo#KEY_USAGE_* constants
*/
@Json(name = "usage")
val usages: List<String>?,
/**
* An object that must have one entry,
* whose name is "ed25519:" followed by the unpadded base64 encoding of the public key,
* and whose value is the unpadded base64 encoding of the public key.
*/
@Json(name = "keys")
val keys: Map<String, String>?,
/**
* Signatures of the key.
* A self-signing or user-signing key must be signed by the master key.
* A master key may be signed by a device.
*/
@Json(name = "signatures")
val signatures: Map<String, Map<String, String>>? = null
) {
fun toCryptoModel(): CryptoCrossSigningKey {
return CryptoInfoMapper.map(this)
}
}

View file

@ -21,7 +21,7 @@ import com.squareup.moshi.Json
* Parent class representing an room key action request * Parent class representing an room key action request
* Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare]
*/ */
open class RoomKeyShare : SendToDeviceObject { internal open class RoomKeyShare : SendToDeviceObject {
var action: String? = null var action: String? = null

View file

@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
* Class representing an room key request cancellation content * Class representing an room key request cancellation content
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class RoomKeyShareCancellation : RoomKeyShare() { internal class RoomKeyShareCancellation : RoomKeyShare() {
init { init {
action = ACTION_SHARE_CANCELLATION action = ACTION_SHARE_CANCELLATION
} }

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