Refactoring / deprecation of MXDeviceInfo

introduced TrustLevels
This commit is contained in:
Valere 2020-01-20 18:13:32 +01:00
parent 98ba2d39a8
commit 6ab540045b
68 changed files with 1117 additions and 560 deletions

View file

@ -19,12 +19,9 @@ package im.vector.matrix.android
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import java.io.File
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import org.junit.Rule
interface InstrumentedTest {
fun context(): Context {
return ApplicationProvider.getApplicationContext()
}

View file

@ -94,7 +94,6 @@ class CommonTestHelper(context: Context) {
syncLiveData.removeObserver(this)
}
}
}
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }

View file

@ -92,7 +92,6 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val lock1 = CountDownLatch(2)
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
}
@ -140,8 +139,6 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(roomJoinedObserver)
}
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
mTestHelper.await(lock2)

View file

@ -4,11 +4,10 @@ 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.*
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.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import org.junit.Assert
import org.junit.Assert.*
import org.junit.FixMethodOrder
import org.junit.Test
@ -16,7 +15,6 @@ 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 {
@ -24,12 +22,10 @@ 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(
@ -41,14 +37,15 @@ class XSigningTest : InstrumentedTest {
val myCrossSigningKeys = aliceSession.getCrossSigningService().getMyCrossSigningKeys()
val masterPubKey = myCrossSigningKeys?.masterKey()
Assert.assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
Assert.assertNotNull("SelfSigned key should be stored", selfSigningKey?.unpaddedBase64PublicKey)
assertNotNull("SelfSigned key should be stored", selfSigningKey?.unpaddedBase64PublicKey)
val userKey = myCrossSigningKeys?.userKey()
Assert.assertNotNull("User key should be stored", userKey?.unpaddedBase64PublicKey)
assertNotNull("User key should be stored", userKey?.unpaddedBase64PublicKey)
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted == true)
Assert.assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted == true)
mTestHelper.signout(aliceSession)
}
@Test
@ -76,21 +73,23 @@ class XSigningTest : InstrumentedTest {
mTestHelper.await(aliceLatch)
mTestHelper.await(bobLatch)
//Check that alice can see bob keys
// 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)
Assert.assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV?.masterKey())
Assert.assertNull("Alice should not see bob User key", bobKeysFromAlicePOV?.userKey())
Assert.assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV?.selfSigningKey())
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())
Assert.assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV?.masterKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
Assert.assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV?.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV?.masterKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV?.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
Assert.assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted == false)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted == false)
mTestHelper.signout(aliceSession)
mTestHelper.signout(bobSession)
}
@Test
@ -118,15 +117,14 @@ class XSigningTest : InstrumentedTest {
mTestHelper.await(aliceLatch)
mTestHelper.await(bobLatch)
//Check that alice can see bob keys
// 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)
Assert.assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted == false)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted == false)
val trustLatch = CountDownLatch(1)
aliceSession.getCrossSigningService().trustUser(bobUserId, object : MatrixCallback<SignatureUploadResponse> {
@ -146,15 +144,14 @@ class XSigningTest : InstrumentedTest {
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<MXDeviceInfo>> {
bobSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onFailure(failure: Throwable) {
fail("Failed to get device")
}
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
fail("Bob should see the new device")
}
@ -181,13 +178,13 @@ class XSigningTest : InstrumentedTest {
// Now alice should cross trust bob's second device
val aliceKeysLatch = CountDownLatch(1)
aliceSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
aliceSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onFailure(failure: Throwable) {
fail("Failed to get device")
}
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
//check that the device is seen
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")
}
@ -196,9 +193,11 @@ class XSigningTest : InstrumentedTest {
})
mTestHelper.await(aliceKeysLatch)
val result = aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
val result = aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId)
assertTrue("Bob second device should be trusted from alice POV", result.isSuccess())
mTestHelper.signout(aliceSession)
mTestHelper.signout(bobSession)
mTestHelper.signout(bobSession2)
}
}

View file

@ -29,12 +29,12 @@ import im.vector.matrix.android.common.*
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.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.MegolmBackupCreationInfo
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.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import org.junit.Assert.*
import org.junit.FixMethodOrder
@ -1161,7 +1161,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup2.isEnabled)
// - Validate the old device from the new one
aliceSession2.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, oldDeviceId, aliceSession2.myUserId)
aliceSession2.setDeviceVerification(DeviceTrustLevel(false, true), oldDeviceId, aliceSession2.myUserId)
// -> Backup should automatically enable on the new device
val latch4 = CountDownLatch(1)

View file

@ -24,7 +24,7 @@ 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.common.CommonTestHelper
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.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
@ -38,7 +38,6 @@ import org.junit.runners.MethodSorters
import java.util.*
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class SASTest : InstrumentedTest {
@ -140,7 +139,6 @@ class SASTest : InstrumentedTest {
var cancelReason: String? = null
val cancelLatch = CountDownLatch(1)
val bobListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
@ -530,8 +528,8 @@ class SASTest : InstrumentedTest {
mTestHelper.await(bobSASLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
// latch wait a bit again
Thread.sleep(1000)

View file

@ -17,14 +17,14 @@
package im.vector.matrix.android.api.extensions
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
/* ==========================================================================================
* MXDeviceInfo
* ========================================================================================== */
fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint()
?.chunked(4)
?.joinToString(separator = " ")

View file

@ -27,6 +27,8 @@ 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.internal.crypto.MXEventDecryptionResult
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.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
@ -48,7 +50,7 @@ interface CryptoService {
fun isCryptoEnabled(): Boolean
fun getSasVerificationService(): SasVerificationService
fun getCrossSigningService(): CrossSigningService
fun getKeysBackupService(): KeysBackupService
@ -57,15 +59,15 @@ interface CryptoService {
fun setWarnOnUnknownDevices(warn: Boolean)
fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String)
fun setDeviceVerification(trustLevel: DeviceTrustLevel, deviceId: String, userId: String)
fun getUserDevices(userId: String): MutableList<MXDeviceInfo>
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
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
@ -81,7 +83,7 @@ interface CryptoService {
fun setRoomBlacklistUnverifiedDevices(roomId: String)
fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo?
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun reRequestRoomKeyForEvent(event: Event)
@ -113,7 +115,7 @@ interface CryptoService {
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 addNewSessionListener(newSessionListener: NewSessionListener)

View file

@ -18,7 +18,7 @@
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 org.matrix.olm.OlmException
@ -36,7 +36,7 @@ sealed class MXCryptoError : Throwable() {
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 {
ENCRYPTING_NOT_ENABLED,

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.crypto.crosssigning
import im.vector.matrix.android.api.MatrixCallback
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.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
@ -25,7 +26,11 @@ interface CrossSigningService {
fun isUserTrusted(userId: String) : Boolean
fun checkUserTrust(userId: String, callback: MatrixCallback<Boolean>? = null)
/**
* 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(userId: String) : UserTrustResult
/**
* Initialize cross signing for this user.
@ -44,5 +49,5 @@ interface CrossSigningService {
*/
fun signDevice(deviceId: String, callback: MatrixCallback<SignatureUploadResponse>)
fun checkDeviceTrust(userId: String, deviceId: String) : DeviceTrustResult
fun checkDeviceTrust(userId: String, deviceId: String, locallyTrusted: Boolean?) : DeviceTrustResult
}

View file

@ -16,32 +16,26 @@
package im.vector.matrix.android.api.session.crypto.crosssigning
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.KeyUsage
data class MXCrossSigningInfo(
/**
* the user id
*/
// @Json(name = "user_id")
var userId: String,
// @Json(name = "user_keys")
var crossSigningKeys: List<CrossSigningKeyInfo> = ArrayList(),
var crossSigningKeys: List<CryptoCrossSigningKey> = ArrayList(),
// TODO this should at the key level no?
val isTrusted: Boolean = false
) {
fun masterKey(): CrossSigningKeyInfo? = crossSigningKeys
.firstOrNull { it.usages?.contains(CrossSigningKeyInfo.KeyUsage.MASTER.value) == 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 userKey(): CrossSigningKeyInfo? = crossSigningKeys
.firstOrNull { it.usages?.contains(CrossSigningKeyInfo.KeyUsage.USER_SIGNING.value) == true }
fun selfSigningKey(): CrossSigningKeyInfo? = crossSigningKeys
.firstOrNull { it.usages?.contains(CrossSigningKeyInfo.KeyUsage.SELF_SIGNING.value) == true }
fun selfSigningKey(): CryptoCrossSigningKey? = crossSigningKeys
.firstOrNull { it.usages?.contains(KeyUsage.SELF_SIGNING.value) == true }
}

View file

@ -1,4 +1,4 @@
///*
// /*
// * Copyright 2020 New Vector Ltd
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
@ -14,15 +14,15 @@
// * limitations under the License.
// */
//
//package im.vector.matrix.android.api.session.crypto.crosssigning
// package im.vector.matrix.android.api.session.crypto.crosssigning
//
//import com.squareup.moshi.Json
//import com.squareup.moshi.JsonClass
//import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
// import com.squareup.moshi.Json
// import com.squareup.moshi.JsonClass
// import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
//
//
//@JsonClass(generateAdapter = true)
//data class MXKeyInfo(
// @JsonClass(generateAdapter = true)
// data class MXKeyInfo(
//
// @Json(name = "public_key")
// val publicKeyBase64: String,
@ -42,7 +42,7 @@
// @Json(name = "signatures")
// var signatures: Map<String, Map<String, String>>? = null
//
//) {
// ) {
//
// data class Builder(
// private val publicKeyBase64: String,
@ -76,5 +76,5 @@
// )
// }
// }
//}
// }
//

View file

@ -45,7 +45,9 @@ 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.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.model.CryptoDeviceInfo
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.MXEncryptEventContentResult
@ -55,9 +57,15 @@ 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.KeysUploadResponse
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.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.*
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.GetDeviceInfoTask
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.UploadKeysTask
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
@ -72,7 +80,12 @@ 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.fetchCopied
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
@ -196,7 +209,7 @@ internal class DefaultCryptoService @Inject constructor(
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
}
override fun getMyDevice(): MXDeviceInfo {
override fun getMyDevice(): CryptoDeviceInfo {
return myDeviceInfoHolder.get().myDevice
}
@ -354,7 +367,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param algorithm the encryption algorithm.
* @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) {
// We only deal in olm keys
null
@ -367,7 +380,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the user 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()) {
cryptoStore.getUserDevice(userId, deviceId)
} else {
@ -398,7 +411,7 @@ internal class DefaultCryptoService @Inject constructor(
// assume if the device is either verified or blocked
// it means that the device is known
if (device?.isUnknown == true) {
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
device.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
isUpdated = true
}
}
@ -419,8 +432,8 @@ internal class DefaultCryptoService @Inject constructor(
* @param deviceId the unique identifier for the device.
* @param userId the owner of the device
*/
override fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String) {
setDeviceVerificationAction.handle(verificationStatus, deviceId, userId)
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, deviceId: String, userId: String) {
setDeviceVerificationAction.handle(trustLevel, deviceId, userId)
}
/**
@ -499,9 +512,8 @@ internal class DefaultCryptoService @Inject constructor(
/**
* @return the stored device keys for a user.
*/
override fun getUserDevices(userId: String): MutableList<MXDeviceInfo> {
val map = cryptoStore.getUserDevices(userId)
return if (null != map) ArrayList(map.values) else ArrayList()
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
}
fun isEncryptionEnabledForInvitedUser(): Boolean {
@ -763,11 +775,15 @@ internal class DefaultCryptoService @Inject constructor(
// Prepare the device keys data to send
// Sign it
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
// 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)
}
@ -1007,8 +1023,8 @@ internal class DefaultCryptoService @Inject constructor(
* @param devicesInRoom the devices map
* @return the unknown devices map
*/
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXUsersDevicesMap<MXDeviceInfo> {
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXUsersDevicesMap<CryptoDeviceInfo> {
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
val userIds = devicesInRoom.userIds
for (userId in userIds) {
devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId ->
@ -1023,7 +1039,7 @@ internal class DefaultCryptoService @Inject constructor(
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) {
runCatching {
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.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.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
@ -166,7 +168,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param userIds the userIds list
* @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) {
for ((k, value) in failures) {
val statusCode = when (val status = value["status"]) {
@ -182,7 +184,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
}
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
val usersDevicesInfoMap = MXUsersDevicesMap<MXDeviceInfo>()
val usersDevicesInfoMap = MXUsersDevicesMap<CryptoDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)
if (null == devices) {
@ -207,6 +209,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
}
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
return usersDevicesInfoMap
}
@ -217,10 +220,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param userIds The users to fetch.
* @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")
// Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>()
val stored = MXUsersDevicesMap<CryptoDeviceInfo>()
// List of user ids we need to download keys for
val downloadUsers = ArrayList<String>()
@ -265,7 +268,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
*
* @param downloadUsers the user ids list
*/
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
// get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
@ -283,49 +286,49 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
for (userId in filteredUsers) {
val devices = response.deviceKeys?.get(userId)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
if (devices != null) {
val mutableDevices = devices.toMutableMap()
for ((deviceId, deviceInfo) in devices) {
// al devices =
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
?.toMutableMap()
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
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(userId, deviceId)
// in some race conditions (like unit tests)
// the self device must be seen as verified
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
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them
mutableDevices.remove(deviceId)
models.remove(deviceId)
if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys
models[deviceId] = previouslyStoredDeviceKeys
}
} else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client.
// So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
models[deviceId]!!.trustLevel = previouslyStoredDeviceKeys.trustLevel
}
}
// Update the store
// 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)?.also {
Timber.d("## CrossSigning : Got keys for $userId : MSK ${it.unpaddedBase64PublicKey}")
// 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)?.also {
val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.d("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
}
val userSigningKey = response.userSigningKeys?.get(userId)?.also {
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.d("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
}
cryptoStore.storeUserCrossSigningKeys(
@ -348,7 +351,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param previouslyStoredDeviceKeys the device keys we received before for this device
* @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) {
Timber.e("## validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
return false
@ -376,14 +379,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
val signKeyId = "ed25519:" + deviceKeys.deviceId
val signKey = deviceKeys.keys?.get(signKeyId)
val signKey = deviceKeys.keys[signKeyId]
if (null == signKey) {
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
return false
}
val signatureMap = deviceKeys.signatures?.get(userId)
val signatureMap = deviceKeys.signatures[userId]
if (null == signatureMap) {
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")

View file

@ -17,7 +17,8 @@
package im.vector.matrix.android.internal.crypto
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.session.SessionScope
import javax.inject.Inject
@ -35,11 +36,13 @@ internal class MyDeviceInfoHolder @Inject constructor(
/**
* my device info
*/
val myDevice: MXDeviceInfo = MXDeviceInfo(credentials.deviceId!!, credentials.userId)
val myDevice: CryptoDeviceInfo
init {
val keys = HashMap<String, String>()
// TODO it's a bit strange, why not load from DB?
if (!olmDevice.deviceEd25519Key.isNullOrEmpty()) {
keys["ed25519:" + credentials.deviceId] = olmDevice.deviceEd25519Key!!
}
@ -48,10 +51,22 @@ internal class MyDeviceInfoHolder @Inject constructor(
keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!!
}
myDevice.keys = keys
// myDevice.keys = keys
//
// myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
myDevice.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
// TODO hwo to really check cross signed status?
//
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
val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId)

View file

@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions
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.MXOlmSessionResult
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,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<MXDeviceInfo>()
suspend fun handle(devicesByUser: Map<String, List<CryptoDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<CryptoDeviceInfo>()
val results = MXUsersDevicesMap<MXOlmSessionResult>()
@ -102,7 +102,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
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
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
it.identityKey() != olmDevice.deviceCurve25519Key
// Don't bother setting up sessions with blocked users
&& !it.isVerified
&& !(it.trustLevel?.isVerified() ?: false)
}
}
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.internal.crypto.MXCRYPTO_ALGORITHM_OLM
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.util.JsonCanonicalizer
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.
* @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 payloadJson = payloadFields.toMutableMap()

View file

@ -16,6 +16,7 @@
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.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.UserId
@ -27,7 +28,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
@UserId private val userId: String,
private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
fun handle(trustLevel: DeviceTrustLevel, deviceId: String, userId: String) {
val device = cryptoStore.getUserDevice(userId, deviceId)
// Sanity check
@ -36,10 +37,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
return
}
if (device.verified != verificationStatus) {
device.verified = verificationStatus
cryptoStore.storeUserDevice(userId, device)
if (device.isVerified != trustLevel.isVerified()) {
if (userId == this.userId) {
// 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
@ -47,5 +45,10 @@ internal class SetDeviceVerificationAction @Inject constructor(
keysBackup.checkAndStartKeysBackup()
}
}
if (device.trustLevel != trustLevel) {
device.trustLevel = trustLevel
cryptoStore.storeUserDevice(userId, device)
}
}
}

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.algorithms.IMXEncrypting
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.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -95,7 +95,7 @@ internal class MXMegolmEncryption(
*
* @param devicesInRoom the devices list
*/
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
var session = outboundSession
if (session == null
// Need to make a brand new session?
@ -106,7 +106,7 @@ internal class MXMegolmEncryption(
outboundSession = session
}
val safeSession = session
val shareMap = HashMap<String, MutableList<MXDeviceInfo>>()/* userId */
val shareMap = HashMap<String, MutableList<CryptoDeviceInfo>>()/* userId */
val userIds = devicesInRoom.userIds
for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
@ -129,14 +129,14 @@ internal class MXMegolmEncryption(
* @param devicesByUsers the devices map
*/
private suspend fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: Map<String, List<MXDeviceInfo>>) {
devicesByUsers: Map<String, List<CryptoDeviceInfo>>) {
// nothing to send, the task is done
if (devicesByUsers.isEmpty()) {
Timber.v("## shareKey() : nothing more to do")
return
}
// 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
for ((userId, devices) in devicesByUsers) {
subMap[userId] = devices
@ -158,7 +158,7 @@ internal class MXMegolmEncryption(
* @param devicesByUser the devices map
*/
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>) {
devicesByUser: Map<String, List<CryptoDeviceInfo>>) {
val sessionKey = olmDevice.getSessionKey(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.
*/
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
// 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
@ -271,8 +271,8 @@ internal class MXMegolmEncryption(
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
val devicesInRoom = MXUsersDevicesMap<CryptoDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
for (userId in keys.userIds) {
val deviceIds = keys.getUserDeviceIds(userId) ?: continue

View file

@ -17,7 +17,7 @@
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 timber.log.Timber
@ -52,7 +52,7 @@ internal class MXOutboundSessionInfo(
* @param devicesInRoom the devices map
* @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
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.MessageEncrypter
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
internal class MXOlmEncryption(
@ -42,7 +42,7 @@ internal class MXOlmEncryption(
//
// TODO: there is a race condition here! What if a new user turns up
ensureSession(userIds)
val deviceInfos = ArrayList<MXDeviceInfo>()
val deviceInfos = ArrayList<CryptoDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
for (device in devices) {

View file

@ -16,7 +16,6 @@
*/
package im.vector.matrix.android.internal.crypto.api
import im.vector.matrix.android.internal.crypto.model.MXKeysObject
import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
@ -66,7 +65,6 @@ internal interface CryptoApi {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query")
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.
@ -75,7 +73,6 @@ internal interface CryptoApi {
@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
@ -95,7 +92,6 @@ internal interface CryptoApi {
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload")
fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): Call<SignatureUploadResponse>
/**
* Claim one-time keys.
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim

View file

@ -26,7 +26,13 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn
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.rest.*
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.KeysQueryResponse
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.UploadResponseFailure
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
@ -42,7 +48,6 @@ import org.matrix.olm.OlmUtility
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class DefaultCrossSigningService @Inject constructor(
@UserId private val userId: String,
@ -55,7 +60,6 @@ internal class DefaultCrossSigningService @Inject constructor(
private val uploadSignaturesTask: UploadSignaturesTask,
private val taskExecutor: TaskExecutor) : CrossSigningService {
private var olmUtility: OlmUtility? = null
private var crossSigningState: CrossSigningState = CrossSigningState.Unknown
@ -68,7 +72,7 @@ internal class DefaultCrossSigningService @Inject constructor(
try {
olmUtility = OlmUtility()
//Try to get stored keys if they exist
// 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")
@ -108,7 +112,6 @@ internal class DefaultCrossSigningService @Inject constructor(
}
}
}
}
} catch (e: Throwable) {
// Mmm this kind of a big issue
@ -116,13 +119,11 @@ internal class DefaultCrossSigningService @Inject constructor(
}
}
fun release() {
olmUtility?.releaseUtility()
listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
}
/**
* - Make 3 key pairs (MSK, USK, SSK)
* - Save the private keys with proper security
@ -136,18 +137,18 @@ internal class DefaultCrossSigningService @Inject constructor(
val myUserID = credentials.userId
//=================
// =================
// 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)
@ -155,39 +156,37 @@ internal class DefaultCrossSigningService @Inject constructor(
Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey")
// Sign userSigningKey with master
val signedUSK = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.USER_SIGNING)
val signedUSK = CryptoCrossSigningKey.Builder(myUserID, 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, CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.SELF_SIGNING)
val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(myUserID, KeyUsage.SELF_SIGNING)
.key(sskPublicKey)
.build().signalableJSONDictionary()).let { masterPkOlm.sign(it) }
// I need to upload the keys
val mskCrossSigningKeyInfo = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.MASTER)
val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.MASTER)
.key(masterPublicKey)
.build()
val params = UploadSigningKeysTask.Params(
masterKey = mskCrossSigningKeyInfo,
userKey = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.USER_SIGNING)
userKey = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.USER_SIGNING)
.key(uskPublicKey)
.signature(myUserID, masterPublicKey, signedUSK)
.build(),
selfSignedKey = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.SELF_SIGNING)
selfSignedKey = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.SELF_SIGNING)
.key(sskPublicKey)
.signature(myUserID, masterPublicKey, signedSSK)
.build(),
@ -253,9 +252,8 @@ internal class DefaultCrossSigningService @Inject constructor(
}
}.executeBy(taskExecutor)
callback?.onSuccess(Unit)
crossSigningState = CrossSigningState.Trusted
callback?.onSuccess(Unit)
}
override fun onFailure(failure: Throwable) {
@ -264,8 +262,6 @@ internal class DefaultCrossSigningService @Inject constructor(
}
}
}.executeBy(taskExecutor)
}
/**
@ -293,40 +289,43 @@ internal class DefaultCrossSigningService @Inject constructor(
/**
* Will not force a download of the key, but will verify signatures trust chain
*/
override fun checkUserTrust(userId: String, callback: MatrixCallback<Boolean>?) {
override fun checkUserTrust(userId: String): UserTrustResult {
Timber.d("## CrossSigning checkUserTrust for $userId")
// 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 myUserKey = cryptoStore.getCrossSigningInfo(credentials.userId)?.userKey()
if (myUserKey == null) {
Timber.d("## CrossSigning checkUserTrust false, CrossSigning is not enabled (userKey not defined)")
callback?.onSuccess(false)
return
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(credentials.userId)
val myUserKey = myCrossSigningInfo?.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(credentials.userId)
if (!myCrossSigningInfo.isTrusted) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
}
// Let's get the other user master key
val masterKey = cryptoStore.getCrossSigningInfo(userId)?.masterKey()
if (masterKey == null) {
Timber.d("## CrossSigning checkUserTrust false for $userId, ")
callback?.onSuccess(false)
return
}
val otherMasterKey = cryptoStore.getCrossSigningInfo(userId)?.masterKey()
?: return UserTrustResult.UnknownCrossSignatureInfo(userId)
val masterKeySignaturesMadeByMyUserKey = masterKey.signatures
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
?.get(credentials.userId) // Signatures made by me
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $userId, not signed by my UserSigningKey")
callback?.onSuccess(false)
return
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
}
override fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? {
@ -338,7 +337,7 @@ internal class DefaultCrossSigningService @Inject constructor(
}
override fun trustUser(userId: String, callback: MatrixCallback<SignatureUploadResponse>) {
//We should have this user keys
// We should have this user keys
val otherMasterKeys = getUserCrossSigningKeys(userId)?.masterKey()
if (otherMasterKeys == null) {
callback.onFailure(Throwable("Other master signing key is not known"))
@ -365,20 +364,17 @@ internal class DefaultCrossSigningService @Inject constructor(
return
}
otherMasterKeys.addSignature(credentials.userId, userPubKey, newSignature)
cryptoStore.setUserKeysAsTrusted(userId, true)
// TODO update local copy with new signature directly here? kind of local echo of trust?
val uploadQuery = UploadSignatureQueryBuilder()
.withSigningKeyInfo(otherMasterKeys)
.withSigningKeyInfo(otherMasterKeys.addSignatureAndCopy(credentials.userId, userPubKey, newSignature))
.build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.callback = callback
}.executeBy(taskExecutor)
}
override fun signDevice(deviceId: String, callback: MatrixCallback<SignatureUploadResponse>) {
// This device should be yours
val device = cryptoStore.getUserDevice(credentials.userId, deviceId)
@ -412,7 +408,7 @@ internal class DefaultCrossSigningService @Inject constructor(
credentials.userId
to
mapOf(
"ed25519:${ssPubKey}" to newSignature
"ed25519:$ssPubKey" to newSignature
)
)
)
@ -440,21 +436,20 @@ internal class DefaultCrossSigningService @Inject constructor(
}.executeBy(taskExecutor)
}
override fun checkDeviceTrust(userId: String, deviceId: String): DeviceTrustResult {
override fun checkDeviceTrust(userId: String, deviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val otherDevice = cryptoStore.getUserDevice(userId, deviceId)
?: return DeviceTrustResult.UnknownDevice(deviceId)
val myKeys = getUserCrossSigningKeys(credentials.userId)
?: return DeviceTrustResult.CrossSigningNotConfigured(credentials.userId)
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(credentials.userId))
if (!myKeys.isTrusted) return DeviceTrustResult.KeysNotTrusted(myKeys)
if (!myKeys.isTrusted) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys))
val otherKeys = getUserCrossSigningKeys(userId)
?: return DeviceTrustResult.CrossSigningNotConfigured(userId)
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
// TODO should we force verification ?
if (!otherKeys.isTrusted) return DeviceTrustResult.KeysNotTrusted(otherKeys)
if (!otherKeys.isTrusted) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys))
// Check if the trust chain is valid
/*
@ -476,18 +471,24 @@ internal class DefaultCrossSigningService @Inject constructor(
*/
val otherSSKSignature = otherDevice.signatures?.get(userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
?: return DeviceTrustResult.MissingDeviceSignature(deviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey
?: "")
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.MissingDeviceSignature(deviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey
?: ""))
// Check bob's device is signed by bob's SSK
try {
olmUtility?.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
} catch (e: Throwable) {
return DeviceTrustResult.InvalidDeviceSignature(deviceId, otherSSKSignature, e)
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(deviceId, otherSSKSignature, e))
}
return DeviceTrustResult.Success(deviceId, true)
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
}
}
}

View file

@ -5,7 +5,7 @@
* 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
* 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,
@ -13,19 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model.rest
package im.vector.matrix.android.internal.crypto.crosssigning
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
data class DeviceTrustLevel(val crossSigningVerified: Boolean, val locallyVerified: Boolean?) {
@JsonClass(generateAdapter = true)
data class UploadSignaturesBody(
@Json(name = "master_key")
val masterKey: CrossSigningKeyInfo? = null,
@Json(name = "self_signing_key")
val selfSigningKey: CrossSigningKeyInfo? = null,
@Json(name = "user_signing_key")
val userSigningKey: CrossSigningKeyInfo? = null
)
fun isVerified() = crossSigningVerified || locallyVerified == true
fun isCrossSigningVerified() = crossSigningVerified
fun isLocallyVerified() = locallyVerified
}

View file

@ -19,13 +19,13 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn
sealed class DeviceTrustResult {
data class Success(val deviceID: String, val crossSigned: Boolean) : 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(): Boolean = this is DeviceTrustResult.Success
fun DeviceTrustResult.isCrossSignedVerified(): Boolean = (this as? DeviceTrustResult.Success)?.level?.isCrossSigningVerified() == true

View file

@ -15,15 +15,14 @@
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
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 MXDeviceInfo.canonicalSignable(): String {
fun CryptoDeviceInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}
fun CrossSigningKeyInfo.canonicalSignable(): String {
fun CryptoCrossSigningKey.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}

View file

@ -0,0 +1,33 @@
/*
* 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()
}

View file

@ -16,7 +16,7 @@
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.
@ -26,7 +26,7 @@ class KeyBackupVersionTrustSignature {
/**
* 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.

View file

@ -16,7 +16,7 @@
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.
@ -32,7 +32,7 @@ class KeysBackupVersionTrustSignature {
* The device that signed the backup version.
* 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.

View file

@ -0,0 +1,86 @@
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
)
}
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")
}
fun CryptoCrossSigningKey.toRest(): RestKeyInfo {
return CryptoInfoMapper.map(this)
}

View file

@ -0,0 +1,102 @@
/*
* 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!!
// )
// }
}
fun CryptoDeviceInfo.toRest(): RestDeviceInfo {
return CryptoInfoMapper.map(this)
}
internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity {
return CryptoMapper.mapToEntity(this)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* 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.
@ -16,7 +16,11 @@
package im.vector.matrix.android.internal.crypto.model
interface MXKeysObject {
/**
* 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
@ -24,6 +28,5 @@ interface MXKeysObject {
val signatures: Map<String, Map<String, String>>?
// fun signalableJSONDictionary(): Map<String, Any>
fun signalableJSONDictionary(): Map<String, Any>
}

View file

@ -0,0 +1,84 @@
/*
* 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 com.squareup.moshi.Moshi
import im.vector.matrix.android.internal.crypto.model.rest.RestDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.RestKeyInfo
import im.vector.matrix.android.internal.di.SerializeNulls
object CryptoInfoMapper {
private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
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

@ -147,14 +147,6 @@ data class MXDeviceInfo(
return map
}
fun addSignature(userId: String, signedWithNoPrefix: String, signature: String) = apply {
val updated = (signatures?.toMutableMap() ?: HashMap())
val userMap = updated[userId]?.toMutableMap()
?: HashMap<String, String>().also { updated[userId] = it }
userMap["ed25519:${signedWithNoPrefix}"] = signature
signatures = updated
}
/**
* @return a dictionary of the parameters
*/

View file

@ -22,7 +22,7 @@ data class MXOlmSessionResult(
/**
* the device
*/
val deviceInfo: MXDeviceInfo,
val deviceInfo: CryptoDeviceInfo,
/**
* Base64 olm session id.
* null if no session could be established.

View file

@ -1,126 +0,0 @@
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.MXKeysObject
/**
* "self_signing_key": {
* "user_id": "@alice:example.com",
* "usage": ["self_signing"],
* "keys": {
* "ed25519:base64+self+signing+public+key": "base64+self+signing+public+key"
* },
* "signatures": {
* "@alice:example.com": {
* "ed25519:base64+master+public+key": "base64+signature"
* }
* }
* }
*/
@JsonClass(generateAdapter = true)
data class CrossSigningKeyInfo(
/**
* The user who owns the key
*/
@Json(name = "user_id")
override var 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")
override var 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")
override var signatures: Map<String, Map<String, String>>? = null
) : MXKeysObject {
// Shortcut to get key as "keys" is an object that must have one entry
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 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
}
fun addSignature(userId: String, signedWithNoPrefix: String, signature: String) = apply {
val updated = (signatures?.toMutableMap() ?: HashMap())
val userMap = updated[userId]?.toMutableMap()
?: HashMap<String, String>().also { updated[userId] = it }
userMap["ed25519:${signedWithNoPrefix}"] = signature
signatures = updated
}
// fun toXSigningKeys(): XSigningKeys {
// return XSigningKeys(
// userId = userId,
// usage = usages ?: emptyList(),
// keys = keys ?: emptyMap(),
// signatures = signatures
//
// )
// }
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(): CrossSigningKeyInfo {
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 CrossSigningKeyInfo(
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")
}
}

View file

@ -18,22 +18,25 @@ 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.MXKeysObject
@JsonClass(generateAdapter = true)
data class DeviceKeys(
@Json(name = "user_id")
override val userId: String,
val userId: String?,
@Json(name = "device_id")
val deviceId: String,
val deviceId: String?,
@Json(name = "algorithms")
val algorithms: List<String>,
val algorithms: List<String>?,
@Json(name = "keys")
override val keys: Map<String, String>,
val keys: Map<String, String>?,
@Json(name = "signatures")
override val signatures: Map<String, Map<String, String>>?
) : MXKeysObject
val signatures: Map<String, Map<String, String>>?,
@Json(name = "usage")
val usage: List<String>? = null
)

View file

@ -18,7 +18,6 @@ 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.MXDeviceInfo
/**
* This class represents the response to /keys/query request made by downloadKeysForUsers
@ -36,7 +35,7 @@ data class KeysQueryResponse(
* TODO Use MXUsersDevicesMap?
*/
@Json(name = "device_keys")
var deviceKeys: Map<String, Map<String, MXDeviceInfo>>? = null,
var deviceKeys: Map<String, Map<String, RestDeviceInfo>>? = null,
/**
* The failures sorted by homeservers. TODO Bad comment ?
@ -45,13 +44,12 @@ data class KeysQueryResponse(
var failures: Map<String, Map<String, Any>>? = null,
@Json(name = "master_keys")
var masterKeys: Map<String, CrossSigningKeyInfo?>? = null,
var masterKeys: Map<String, RestKeyInfo?>? = null,
@Json(name = "self_signing_keys")
var selfSigningKeys: Map<String, CrossSigningKeyInfo?>? = null,
var selfSigningKeys: Map<String, RestKeyInfo?>? = null,
@Json(name = "user_signing_keys")
var userSigningKeys: Map<String, CrossSigningKeyInfo?>? = null
var userSigningKeys: Map<String, RestKeyInfo?>? = null
)

View file

@ -23,7 +23,7 @@ import im.vector.matrix.android.api.util.JsonDict
@JsonClass(generateAdapter = true)
data class KeysUploadBody(
@Json(name = "device_keys")
var deviceKeys: DeviceKeys? = null,
var deviceKeys: RestDeviceInfo? = null,
@Json(name = "one_time_keys")
var oneTimeKeys: JsonDict? = null

View file

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

@ -19,7 +19,6 @@ 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.MXDeviceInfo
/**
* Upload Signature response
@ -38,7 +37,6 @@ data class SignatureUploadResponse(
)
@JsonClass(generateAdapter = true)
data class UploadResponseFailure(
@Json(name = "status")

View file

@ -15,38 +15,39 @@
*/
package im.vector.matrix.android.internal.crypto.model.rest
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXKeysObject
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.toRest
/**
* Helper class to build CryptoApi#uploadSignatures params
*/
data class UploadSignatureQueryBuilder(
private val deviceInfoList: ArrayList<MXDeviceInfo> = ArrayList(),
private val signingKeyInfoList: ArrayList<CrossSigningKeyInfo> = ArrayList()
private val deviceInfoList: ArrayList<CryptoDeviceInfo> = ArrayList(),
private val signingKeyInfoList: ArrayList<CryptoCrossSigningKey> = ArrayList()
) {
fun withDeviceInfo(deviceInfo: MXDeviceInfo) = apply {
fun withDeviceInfo(deviceInfo: CryptoDeviceInfo) = apply {
deviceInfoList.add(deviceInfo)
}
fun withSigningKeyInfo(info: CrossSigningKeyInfo) = apply {
fun withSigningKeyInfo(info: CryptoCrossSigningKey) = apply {
signingKeyInfoList.add(info)
}
fun build(): Map<String, Map<String, MXKeysObject>> {
val map = HashMap<String, HashMap<String, MXKeysObject>>()
fun build(): Map<String, Map<String, @JvmSuppressWildcards Any>> {
val map = HashMap<String, HashMap<String, Any>>()
val usersList = (deviceInfoList.map { it.userId } + signingKeyInfoList.mapNotNull { it.userId }).distinct()
usersList.forEach { userID ->
val userMap = HashMap<String, MXKeysObject>()
val userMap = HashMap<String, Any>()
deviceInfoList.filter { it.userId == userID }.forEach { deviceInfo ->
userMap[deviceInfo.deviceId] = deviceInfo.toDeviceKeys()
userMap[deviceInfo.deviceId] = deviceInfo.toRest()
}
signingKeyInfoList.filter { it.userId == userID }.forEach { keyInfo ->
keyInfo.unpaddedBase64PublicKey?.let { base64Key ->
userMap[base64Key] = keyInfo
userMap[base64Key] = keyInfo.toRest()
}
}
map[userID] = userMap
@ -54,5 +55,4 @@ data class UploadSignatureQueryBuilder(
return map
}
}

View file

@ -17,18 +17,17 @@ 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.auth.registration.AuthParams
@JsonClass(generateAdapter = true)
internal data class UploadSigningKeysBody(
@Json(name = "master_key")
val masterKey: CrossSigningKeyInfo? = null,
val masterKey: RestKeyInfo? = null,
@Json(name = "self_signing_key")
val selfSigningKey: CrossSigningKeyInfo? = null,
val selfSigningKey: RestKeyInfo? = null,
@Json(name = "user_signing_key")
val userSigningKey: CrossSigningKeyInfo? = null,
val userSigningKey: RestKeyInfo? = null,
@Json(name = "auth")
val auth: UserPasswordAuth? = null

View file

@ -1,20 +0,0 @@
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.MXKeysObject
@JsonClass(generateAdapter = true)
data class XSigningKeys(
@Json(name = "user_id")
override val userId: String,
@Json(name = "usage")
val usage: List<String>,
@Json(name = "keys")
override val keys: Map<String, String>,
@Json(name = "signatures")
override val signatures: Map<String, Map<String, String>>?
) : MXKeysObject

View file

@ -21,10 +21,10 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount
@ -156,7 +156,7 @@ internal interface IMXCryptoStore {
* @param userId the user's id.
* @param device the device to store.
*/
fun storeUserDevice(userId: String?, deviceInfo: MXDeviceInfo?)
fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?)
/**
* Retrieve a device for a user.
@ -165,7 +165,7 @@ internal interface IMXCryptoStore {
* @param userId the user's id.
* @return the device
*/
fun getUserDevice(userId: String, deviceId: String): MXDeviceInfo?
fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo?
/**
* Retrieve a device by its identity key.
@ -173,7 +173,7 @@ internal interface IMXCryptoStore {
* @param identityKey the device identity key (`MXDeviceInfo.identityKey`)
* @return the device or null if not found
*/
fun deviceWithIdentityKey(identityKey: String): MXDeviceInfo?
fun deviceWithIdentityKey(identityKey: String): CryptoDeviceInfo?
/**
* Store the known devices for a user.
@ -181,12 +181,11 @@ internal interface IMXCryptoStore {
* @param userId The user's id.
* @param devices A map from device id to 'MXDevice' object for the device.
*/
fun storeUserDevices(userId: String, devices: Map<String, MXDeviceInfo>?)
fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?)
fun storeUserCrossSigningKeys(userId: String, masterKey: CrossSigningKeyInfo?,
selfSigningKey: CrossSigningKeyInfo?,
userSigningKey: CrossSigningKeyInfo?)
fun storeUserCrossSigningKeys(userId: String, masterKey: CryptoCrossSigningKey?,
selfSigningKey: CryptoCrossSigningKey?,
userSigningKey: CryptoCrossSigningKey?)
/**
* Retrieve the known devices for a user.
@ -194,7 +193,7 @@ internal interface IMXCryptoStore {
* @param userId The user's id.
* @return The devices map if some devices are known, else null
*/
fun getUserDevices(userId: String): Map<String, MXDeviceInfo>?
fun getUserDevices(userId: String): Map<String, CryptoDeviceInfo>?
/**
* Store the crypto algorithm for a room.
@ -389,10 +388,9 @@ internal interface IMXCryptoStore {
fun removeSessionListener(listener: NewSessionListener)
//=============================================
// =============================================
// CROSS SIGNING
//=============================================
// =============================================
/**
* Gets the current crosssigning info
@ -400,11 +398,9 @@ internal interface IMXCryptoStore {
fun getMyCrossSigningInfo() : MXCrossSigningInfo?
fun setMyCrossSigningInfo(info: MXCrossSigningInfo?)
fun getCrossSigningInfo(userId: String) : MXCrossSigningInfo?
fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?)
fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?)
fun getCrossSigningPrivateKeys() : PrivateKeysInfo?

View file

@ -21,14 +21,36 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.toEntity
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
import im.vector.matrix.android.internal.crypto.store.db.model.*
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMapper
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntity
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntity
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntity
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey
import im.vector.matrix.android.internal.crypto.store.db.query.delete
import im.vector.matrix.android.internal.crypto.store.db.query.get
import im.vector.matrix.android.internal.crypto.store.db.query.getById
@ -171,20 +193,22 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
return olmAccount
}
override fun storeUserDevice(userId: String?, deviceInfo: MXDeviceInfo?) {
override fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?) {
if (userId == null || deviceInfo == null) {
return
}
doRealmTransaction(realmConfiguration) {
val user = UserEntity.getOrCreate(it, userId)
doRealmTransaction(realmConfiguration) { realm ->
val user = UserEntity.getOrCreate(realm, userId)
// Create device info
val deviceInfoEntity = DeviceInfoEntity.getOrCreate(it, userId, deviceInfo.deviceId).apply {
deviceId = deviceInfo.deviceId
identityKey = deviceInfo.identityKey()
putDeviceInfo(deviceInfo)
}
val deviceInfoEntity = CryptoMapper.mapToEntity(deviceInfo)
realm.insertOrUpdate(deviceInfoEntity)
// val deviceInfoEntity = DeviceInfoEntity.getOrCreate(it, userId, deviceInfo.deviceId).apply {
// deviceId = deviceInfo.deviceId
// identityKey = deviceInfo.identityKey()
// putDeviceInfo(deviceInfo)
// }
if (!user.devices.contains(deviceInfoEntity)) {
user.devices.add(deviceInfoEntity)
@ -192,25 +216,28 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
}
override fun getUserDevice(userId: String, deviceId: String): MXDeviceInfo? {
override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
return doRealmQueryAndCopy(realmConfiguration) {
it.where<DeviceInfoEntity>()
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
.findFirst()
}?.let {
CryptoMapper.mapToModel(it)
}
?.getDeviceInfo()
}
override fun deviceWithIdentityKey(identityKey: String): MXDeviceInfo? {
override fun deviceWithIdentityKey(identityKey: String): CryptoDeviceInfo? {
return doRealmQueryAndCopy(realmConfiguration) {
it.where<DeviceInfoEntity>()
.equalTo(DeviceInfoEntityFields.IDENTITY_KEY, identityKey)
.findFirst()
}
?.getDeviceInfo()
?.let {
CryptoMapper.mapToModel(it)
}
}
override fun storeUserDevices(userId: String, devices: Map<String, MXDeviceInfo>?) {
override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
doRealmTransaction(realmConfiguration) { realm ->
if (devices == null) {
// Remove the user
@ -221,25 +248,18 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
// Add the devices
// Ensure all other devices are deleted
u.devices.deleteAllFromRealm()
u.devices.addAll(
devices.map {
DeviceInfoEntity.getOrCreate(realm, userId, it.value.deviceId).apply {
deviceId = it.value.deviceId
identityKey = it.value.identityKey()
putDeviceInfo(it.value)
}
}
)
val new = devices.map { entry -> entry.value.toEntity() }
new.forEach { realm.insertOrUpdate(it) }
u.devices.addAll(new)
}
}
}
}
override fun storeUserCrossSigningKeys(userId: String,
masterKey: CrossSigningKeyInfo?,
selfSigningKey: CrossSigningKeyInfo?,
userSigningKey: CrossSigningKeyInfo?) {
masterKey: CryptoCrossSigningKey?,
selfSigningKey: CryptoCrossSigningKey?,
userSigningKey: CryptoCrossSigningKey?) {
doRealmTransaction(realmConfiguration) { realm ->
UserEntity.getOrCreate(realm, userId)
.let { userEntity ->
@ -253,7 +273,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
val existingMaster = signingInfo.getMasterKey()
if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) {
//update signatures?
// update signatures?
existingMaster.putSignatures(masterKey.signatures)
existingMaster.usages = masterKey.usages?.toTypedArray()?.let { RealmList(*it) }
?: RealmList()
@ -269,7 +289,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
val existingSelfSigned = signingInfo.getSelfSignedKey()
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) {
//update signatures?
// update signatures?
existingSelfSigned.putSignatures(selfSigningKey.signatures)
existingSelfSigned.usages = selfSigningKey.usages?.toTypedArray()?.let { RealmList(*it) }
?: RealmList()
@ -288,7 +308,6 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
}
}
}
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
@ -304,7 +323,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) {
doRealmTransaction(realmConfiguration) {realm ->
doRealmTransaction(realmConfiguration) { realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignMasterPrivateKey = msk
xSignSelfSignedPrivateKey = ssk
@ -313,15 +332,14 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
}
override fun getUserDevices(userId: String): Map<String, MXDeviceInfo>? {
override fun getUserDevices(userId: String): Map<String, CryptoDeviceInfo>? {
return doRealmQueryAndCopy(realmConfiguration) {
it.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, userId)
.findFirst()
}
?.devices
?.mapNotNull { it.getDeviceInfo() }
?.map { CryptoMapper.mapToModel(it) }
?.associateBy { it.deviceId }
}
@ -836,10 +854,19 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) {
doRealmTransaction(realmConfiguration) { realm ->
realm.where(CrossSigningInfoEntity::class.java)
val info = realm.where(CrossSigningInfoEntity::class.java)
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
.findFirst()
?.isTrusted = trusted
if (info != null) {
val level = info.trustLevelEntity
if (level == null) {
val newLevel = realm.createObject(TrustLevelEntity::class.java)
newLevel.locallyVerified = true
info.trustLevelEntity = newLevel
} else {
level.locallyVerified = true
}
}
}
}
@ -853,7 +880,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
userId = userId,
crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull {
val pubKey = it.publicKeyBase64 ?: return@mapNotNull null
CrossSigningKeyInfo(
CryptoCrossSigningKey(
userId = userId,
keys = mapOf("ed25519:$pubKey" to pubKey),
usages = it.usages.map { it },
@ -861,7 +888,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
)
},
isTrusted = xsignInfo.isTrusted
isTrusted = xsignInfo.trustLevelEntity?.isVerified() ?: false // TODO
)
}
}
@ -889,6 +916,15 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
)
}
existing.crossSigningKeys = xkeys
val existingTrust = existing.trustLevelEntity
if (existingTrust != null) {
existingTrust.locallyVerified = true
} else {
realm.createObject(TrustLevelEntity::class.java).let {
it.locallyVerified = info.isTrusted
existing.trustLevelEntity = it
}
}
}
}
}

View file

@ -16,8 +16,17 @@
package im.vector.matrix.android.internal.crypto.store.db
import im.vector.matrix.android.internal.crypto.store.db.model.*
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
import im.vector.matrix.android.internal.di.SerializeNulls
import io.realm.DynamicRealm
import io.realm.RealmMigration
import timber.log.Timber
@ -39,27 +48,88 @@ internal object RealmCryptoStoreMigration : RealmMigration {
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity")
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED,true)
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED,true)
Timber.d("Create CrossSigningInfoEntity")
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
.addField(CrossSigningInfoEntityFields.IS_TRUSTED, Boolean::class.java)
.addRealmObjectField(CrossSigningInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema)
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
Timber.d("Updating UserEntity table")
realm.schema.get("UserEntity")
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
List::class.java,
String::class.java,
Any::class.java
))
val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
))
realm.schema.get("DeviceInfoEntity")
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED,true)
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`,trustLevelentityEntitySchema)
?.transform { obj ->
val oldSerializedData = obj.getString("deviceInfoData")
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
val trustLevel = realm.createObject("TrustLevelEntity")
when (oldDevice.verified) {
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
}
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
}
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
}
}
?.removeField("deviceInfoData")
}
}
}

View file

@ -34,6 +34,7 @@ import io.realm.annotations.RealmModule
OutgoingRoomKeyRequestEntity::class,
UserEntity::class,
KeyInfoEntity::class,
CrossSigningInfoEntity::class
CrossSigningInfoEntity::class,
TrustLevelEntity::class
])
internal class RealmCryptoStoreModule

View file

@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.crypto.store.db.model
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
import im.vector.matrix.android.internal.crypto.model.KeyUsage
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
@ -25,27 +25,26 @@ internal open class CrossSigningInfoEntity(
@PrimaryKey
var userId: String? = null,
var crossSigningKeys: RealmList<KeyInfoEntity> = RealmList(),
var isTrusted: Boolean = false
var trustLevelEntity: TrustLevelEntity? = null
) : RealmObject() {
companion object
fun getMasterKey() = crossSigningKeys.firstOrNull { it.usages.contains(CrossSigningKeyInfo.KeyUsage.MASTER.value) }
fun getMasterKey() = crossSigningKeys.firstOrNull { it.usages.contains(KeyUsage.MASTER.value) }
fun setMasterKey(info: KeyInfoEntity?) {
crossSigningKeys
.filter { it.usages.contains(CrossSigningKeyInfo.KeyUsage.MASTER.value) }
.filter { it.usages.contains(KeyUsage.MASTER.value) }
.forEach { crossSigningKeys.remove(it) }
info?.let { crossSigningKeys.add(it) }
}
fun getSelfSignedKey() = crossSigningKeys.firstOrNull { it.usages.contains(CrossSigningKeyInfo.KeyUsage.SELF_SIGNING.value) }
fun getSelfSignedKey() = crossSigningKeys.firstOrNull { it.usages.contains(KeyUsage.SELF_SIGNING.value) }
fun setSelfSignedKey(info: KeyInfoEntity?) {
crossSigningKeys
.filter { it.usages.contains(CrossSigningKeyInfo.KeyUsage.SELF_SIGNING.value) }
.filter { it.usages.contains(KeyUsage.SELF_SIGNING.value) }
.forEach { crossSigningKeys.remove(it) }
info?.let { crossSigningKeys.add(it) }
}
}

View file

@ -0,0 +1,110 @@
/*
* 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.store.db.model
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
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.CryptoDeviceInfo
import im.vector.matrix.android.internal.di.SerializeNulls
import timber.log.Timber
object CryptoMapper {
private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
private val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
List::class.java,
String::class.java,
Any::class.java
))
private val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
))
private val mapOfStringMigrationAdapter = moshi.adapter<Map<String, Map<String, String>>>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
))
internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
return DeviceInfoEntity(
primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId),
userId = deviceInfo.userId,
deviceId = deviceInfo.deviceId,
algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms),
keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys),
signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures),
isBlocked = deviceInfo.isBlocked,
trustLevelEntity = deviceInfo.trustLevel?.let {
TrustLevelEntity(
crossSignedVerified = it.crossSigningVerified,
locallyVerified = it.locallyVerified
)
},
unsignedMapJson = mapMigrationAdapter.toJson(deviceInfo.unsigned)
)
}
internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo {
return CryptoDeviceInfo(
userId = deviceInfoEntity.userId ?: "",
deviceId = deviceInfoEntity.deviceId ?: "",
isBlocked = deviceInfoEntity.isBlocked ?: false,
trustLevel = deviceInfoEntity.trustLevelEntity?.let {
DeviceTrustLevel(it.crossSignedVerified ?: false, it.locallyVerified)
},
unsigned = deviceInfoEntity.unsignedMapJson?.let {
try {
mapMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
signatures = deviceInfoEntity.signatureMapJson?.let {
try {
mapOfStringMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
keys = deviceInfoEntity.keysMapJson?.let {
try {
moshi.adapter<Map<String, String>>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)).fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
algorithms = deviceInfoEntity.algorithmListJson?.let {
try {
listMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
}
)
}
}

View file

@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.store.db.model
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.olm.OlmAccount

View file

@ -16,9 +16,6 @@
package im.vector.matrix.android.internal.crypto.store.db.model
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
@ -30,18 +27,26 @@ internal fun DeviceInfoEntity.Companion.createPrimaryKey(userId: String, deviceI
internal open class DeviceInfoEntity(@PrimaryKey var primaryKey: String = "",
var deviceId: String? = null,
var identityKey: String? = null,
var deviceInfoData: String? = null)
// var deviceInfoData: String? = null,
var userId: String? = null,
var isBlocked: Boolean? = null,
var algorithmListJson: String? = null,
var keysMapJson: String? = null,
var signatureMapJson: String? = null,
var unsignedMapJson: String? = null,
var trustLevelEntity: TrustLevelEntity? = null
)
: RealmObject() {
// Deserialize data
fun getDeviceInfo(): MXDeviceInfo? {
return deserializeFromRealm(deviceInfoData)
}
// Serialize data
fun putDeviceInfo(deviceInfo: MXDeviceInfo?) {
deviceInfoData = serializeForRealm(deviceInfo)
}
// // Deserialize data
// fun getDeviceInfo(): MXDeviceInfo? {
// return deserializeFromRealm(deviceInfoData)
// }
//
// // Serialize data
// fun putDeviceInfo(deviceInfo: MXDeviceInfo?) {
// deviceInfoData = serializeForRealm(deviceInfo)
// }
@LinkingObjects("devices")
val users: RealmResults<UserEntity>? = null

View file

@ -21,7 +21,6 @@ import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
import io.realm.RealmList
import io.realm.RealmObject
internal open class KeyInfoEntity(
var publicKeyBase64: String? = null,
// var isTrusted: Boolean = false,

View file

@ -0,0 +1,13 @@
package im.vector.matrix.android.internal.crypto.store.db.model
import io.realm.RealmObject
internal open class TrustLevelEntity(
var crossSignedVerified: Boolean? = null,
var locallyVerified: Boolean? = null
) : RealmObject() {
companion object
fun isVerified() : Boolean = crossSignedVerified == true || locallyVerified == true
}

View file

@ -34,4 +34,3 @@ internal fun CrossSigningInfoEntity.Companion.get(realm: Realm, userId: String):
.equalTo(UserEntityFields.USER_ID, userId)
.findFirst()
}

View file

@ -18,9 +18,9 @@ package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.RestDeviceInfo
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.convertToUTF8
@ -30,7 +30,7 @@ import javax.inject.Inject
internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadResponse> {
data class Params(
// the device keys to send.
val deviceKeys: DeviceKeys?,
val deviceKeys: RestDeviceInfo?,
// the one-time keys to send.
val oneTimeKeys: JsonDict?,
// the explicit device_id to use for upload (default is to use the same as that used during auth).

View file

@ -17,17 +17,15 @@ package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.MXKeysObject
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, SignatureUploadResponse> {
data class Params(
val signatures: Map<String, Map<String, MXKeysObject>>
val signatures: Map<String, Map<String, Any>>
)
}
@ -41,10 +39,9 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
apiCall = cryptoApi.uploadSignatures(params.signatures)
}
if (executeRequest.failures?.isNotEmpty() == true) {
//TODO better
// TODO better
throw Failure.OtherServerError(executeRequest.toString(), 400)
}
return executeRequest
}
}

View file

@ -19,25 +19,25 @@ package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
import im.vector.matrix.android.internal.crypto.model.rest.UploadSigningKeysBody
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.internal.crypto.model.toRest
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface UploadSigningKeysTask : Task<UploadSigningKeysTask.Params, KeysQueryResponse> {
data class Params(
// the device keys to send.
val masterKey: CrossSigningKeyInfo,
val masterKey: CryptoCrossSigningKey,
// the one-time keys to send.
val userKey: CrossSigningKeyInfo,
val userKey: CryptoCrossSigningKey,
// the explicit device_id to use for upload (default is to use the same as that used during auth).
val selfSignedKey: CrossSigningKeyInfo,
val selfSignedKey: CryptoCrossSigningKey,
val userPasswordAuth: UserPasswordAuth?
)
}
@ -47,11 +47,10 @@ internal class DefaultUploadSigningKeysTask @Inject constructor(
private val eventBus: EventBus
) : UploadSigningKeysTask {
override suspend fun execute(params: UploadSigningKeysTask.Params): KeysQueryResponse {
val uploadQuery = UploadSigningKeysBody(
masterKey = params.masterKey,
userSigningKey = params.userKey,
selfSigningKey = params.selfSignedKey,
masterKey = params.masterKey.toRest(),
userSigningKey = params.userKey.toRest(),
selfSigningKey = params.selfSignedKey.toRest(),
auth = params.userPasswordAuth.takeIf { params.userPasswordAuth?.session != null }
)
try {
@ -83,10 +82,6 @@ internal class DefaultUploadSigningKeysTask @Inject constructor(
}
// Other error
throw throwable
}
}
}

View file

@ -32,7 +32,8 @@ import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
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.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -202,7 +203,7 @@ internal class DefaultSasVerificationService @Inject constructor(
}
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED,
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
deviceID,
userId)
@ -398,7 +399,7 @@ internal class DefaultSasVerificationService @Inject constructor(
}
private suspend fun checkKeysAreDownloaded(otherUserId: String,
fromDevice: String): MXUsersDevicesMap<MXDeviceInfo>? {
fromDevice: String): MXUsersDevicesMap<CryptoDeviceInfo>? {
return try {
var keys = deviceListManager.downloadKeys(listOf(otherUserId), false)
if (keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true) {

View file

@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.crypto.sas.SasMode
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
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.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -168,7 +168,7 @@ internal abstract class SASVerificationTransaction(
?.masterKey()
?.unpaddedBase64PublicKey
?.let { masterPublicKey ->
val crossSigningKeyId = "ed25519:${masterPublicKey}"
val crossSigningKeyId = "ed25519:$masterPublicKey"
macUsingAgreedMethod(masterPublicKey, baseInfo + crossSigningKeyId)?.let { MSKMacString ->
keyMap[crossSigningKeyId] = MSKMacString
}
@ -179,12 +179,10 @@ internal abstract class SASVerificationTransaction(
if (macString.isNullOrBlank() || keyStrings.isNullOrBlank()) {
// Should not happen
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
cancel(CancelCode.UnexpectedMessage)
return
}
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
myMac = macMsg
state = SasVerificationTxState.SendingMac
@ -277,11 +275,11 @@ internal abstract class SASVerificationTransaction(
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
if (otherCrossSigningMasterKeyPublic != null) {
//Did the user signed his master key
// Did the user signed his master key
theirMac!!.mac!!.keys.forEach {
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
//Check the signature
// Check the signature
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
if (mac != theirMac?.mac?.get(it)) {
// WRONG!
@ -303,12 +301,23 @@ internal abstract class SASVerificationTransaction(
return
}
if (otherMasterKeyIsVerified) {
// If not me sign his MSK and upload the signature
if (otherMasterKeyIsVerified && otherUserId != credentials.userId) {
// we should trust this master key
// And check verification MSK -> SSK?
crossSigningService.trustUser(otherUserId, object : MatrixCallback<SignatureUploadResponse> {
override fun onFailure(failure: Throwable) {
Timber.e(failure,"## SAS Verification: Failed to trust User $otherUserId")
Timber.e(failure, "## SAS Verification: Failed to trust User $otherUserId")
}
})
}
if (otherUserId == credentials.userId) {
// If me it's reasonable to sign and upload the device signature
// Notice that i might not have the private keys, so may ot be able to do it
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<SignatureUploadResponse> {
override fun onFailure(failure: Throwable) {
Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId")
}
})
}
@ -322,7 +331,8 @@ internal abstract class SASVerificationTransaction(
}
private fun setDeviceVerified(deviceId: String, userId: String) {
setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED,
// TODO should not override cross sign status
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
deviceId,
userId)
}

View file

@ -77,7 +77,6 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
}
crypto.downloadKeys(listOf("@testxsigningvfe:matrix.org"), true, object : MatrixCallback<Any> {
})
var error: Throwable? = null

View file

@ -16,6 +16,8 @@
package im.vector.matrix.android.internal.task
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
@ -126,4 +128,48 @@ class CoroutineSequencersTest {
println("BLOCKING METHOD $name ENDS on ${Thread.currentThread().name}")
return name
}
@Test
fun test_MoshiMap() {
val moshi = Moshi.Builder().build()
val sample = """
{
"master_key": {
"user_id": "@alice:example.com",
"usage": ["master"],
"keys": {
"ed25519:base64+master+public+key": "base64+self+master+key",
}
},
"self_signing_key": {
"user_id": "@alice:example.com",
"usage": ["self_signing"],
"keys": {
"ed25519:base64+self+signing+public+key": "base64+self+signing+public+key",
},
"signatures": {
"@alice:example.com": {
"ed25519:base64+master+public+key": "base64+signature"
}
}
},
"user_signing_key": {
"user_id": "@alice:example.com",
"keys": {
"ed25519:base64+device+signing+public+key": "base64+device+signing+public+key",
},
"usage": ["user_signing"],
"signatures": {
"@alice:example.com": {
"ed25519:base64+master+public+key": "base64+signature"
}
}
}
""".trimIndent()
val adapter = moshi.adapter<Map<String, Any>>(Types.newParameterizedType(Map::class.java, List::class.java, String::class.java))
val map = adapter.fromJson(sample)
print(map)
}
}

View file

@ -44,7 +44,6 @@ class DebugMenuActivity : VectorBaseActivity() {
override fun getLayoutRes() = R.layout.activity_debug_menu
@Inject
lateinit var activeSessionHolder: ActiveSessionHolder
@ -164,7 +163,6 @@ class DebugMenuActivity : VectorBaseActivity() {
@OnClick(R.id.debug_initialise_xsigning)
fun testXSigning() {
activeSessionHolder.getActiveSession().getCrossSigningService().initializeCrossSigning(null, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
if (failure is Failure.OtherServerError
&& failure.httpCode == 401

View file

@ -28,7 +28,8 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransactio
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
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.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
@ -98,8 +99,8 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
alertsToRequests[mappingKey] = ArrayList<IncomingRoomKeyRequest>().apply { this.add(request) }
// Add a notification for every incoming request
session?.downloadKeys(listOf(userId), false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
session?.downloadKeys(listOf(userId), false, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
val deviceInfo = data.getObject(userId, deviceId)
if (null == deviceInfo) {
@ -109,9 +110,9 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
}
if (deviceInfo.isUnknown) {
session?.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED, deviceId, userId)
session?.setDeviceVerification(DeviceTrustLevel(false, false), deviceId, userId)
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
deviceInfo.trustLevel = DeviceTrustLevel(false, false)
// can we get more info on this device?
session?.getDevicesList(object : MatrixCallback<DevicesListResponse> {
@ -143,7 +144,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
userId: String,
deviceId: String,
wasNewDevice: Boolean,
deviceInfo: MXDeviceInfo?,
deviceInfo: CryptoDeviceInfo?,
moreInfo: DeviceInfo? = null) {
val deviceName = if (deviceInfo!!.displayName().isNullOrEmpty()) deviceInfo.deviceId else deviceInfo.displayName()
val dialogText: String?

View file

@ -101,7 +101,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
it.otherUserMxItem?.let { matrixItem ->
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
if(it.sasTransactionState == SasVerificationTxState.Verified) {
if (it.sasTransactionState == SasVerificationTxState.Verified) {
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
otherUserShield.isVisible = true
} else {

View file

@ -50,7 +50,6 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.BuildConfig
import im.vector.riotx.R
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel