fix broken test, userId confustion

cleaning


cleaning


add tests


cleaning
This commit is contained in:
Valere 2022-08-02 16:27:29 +02:00
parent c8f0792997
commit ac0e5e9dec
7 changed files with 442 additions and 59 deletions

View File

@ -365,7 +365,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
} }
testHelper.retryPeriodically { testHelper.retryPeriodically {
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
} }
} }

View File

@ -25,7 +25,6 @@ import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Assert.fail import org.junit.Assert.fail
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
@ -34,6 +33,7 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
@ -48,7 +48,6 @@ import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest @LargeTest
@Ignore
class XSigningTest : InstrumentedTest { class XSigningTest : InstrumentedTest {
@Test @Test
@ -214,4 +213,101 @@ class XSigningTest : InstrumentedTest {
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null) val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified()) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
} }
@Test
fun testWarnOnCrossSigningReset() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceAuthParams = UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD
)
val bobAuthParams = UserPasswordAuth(
user = bobSession!!.myUserId,
password = TestConstants.PASSWORD
)
testHelper.doSync<Unit> {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams)
}
}, it)
}
testHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
}, it)
}
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
aliceSession.cryptoService().crossSigningService().isUserTrusted(bobSession.myUserId)
}
}
aliceSession.cryptoService().crossSigningService().checkUserTrust(bobSession.myUserId).let {
assertTrue(it is UserTrustResult.Success)
}
val currentBobMSK = aliceSession.cryptoService().crossSigningService()
.getUserCrossSigningKeys(bobSession.myUserId)!!
.masterKey()!!.unpaddedBase64PublicKey!!
testHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
}, it)
}
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val newBobMsk = aliceSession.cryptoService().crossSigningService()
.getUserCrossSigningKeys(bobSession.myUserId)
?.masterKey()?.unpaddedBase64PublicKey
newBobMsk != null && newBobMsk != currentBobMSK
}
}
// trick to force event to sync
bobSession.roomService().getRoom(cryptoTestData.roomId)!!.typingService().userIsTyping()
// assert that bob is not trusted anymore from alice s
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val trust = aliceSession.cryptoService().crossSigningService().checkUserTrust(bobSession.myUserId)
!trust.isVerified()
}
}
// trick to force event to sync
bobSession.roomService().getRoom(cryptoTestData.roomId)!!.typingService().userStopsTyping()
bobSession.roomService().getRoom(cryptoTestData.roomId)!!.typingService().userIsTyping()
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val info = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
info?.wasTrustedOnce == true
}
}
// trick to force event to sync
bobSession.roomService().getRoom(cryptoTestData.roomId)!!.typingService().userStopsTyping()
bobSession.roomService().getRoom(cryptoTestData.roomId)!!.typingService().userIsTyping()
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
!aliceSession.cryptoService().crossSigningService().isUserTrusted(bobSession.myUserId)
}
}
}
} }

View File

@ -60,7 +60,7 @@ import javax.inject.Inject
@SessionScope @SessionScope
internal class DefaultCrossSigningService @Inject constructor( internal class DefaultCrossSigningService @Inject constructor(
@UserId private val userId: String, @UserId private val myUserId: String,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
@ -127,7 +127,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
// Recover local trust in case private key are there? // Recover local trust in case private key are there?
setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified()) setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
} }
} catch (e: Throwable) { } catch (e: Throwable) {
// Mmm this kind of a big issue // Mmm this kind of a big issue
@ -168,12 +168,12 @@ internal class DefaultCrossSigningService @Inject constructor(
override fun onSuccess(data: InitializeCrossSigningTask.Result) { override fun onSuccess(data: InitializeCrossSigningTask.Result) {
val crossSigningInfo = MXCrossSigningInfo( val crossSigningInfo = MXCrossSigningInfo(
userId, myUserId,
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo), listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
true true
) )
cryptoStore.setMyCrossSigningInfo(crossSigningInfo) cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
setUserKeysAsTrusted(userId, true) setUserKeysAsTrusted(myUserId, true)
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
@ -270,7 +270,7 @@ internal class DefaultCrossSigningService @Inject constructor(
uskKeyPrivateKey: String?, uskKeyPrivateKey: String?,
sskPrivateKey: String? sskPrivateKey: String?
): UserTrustResult { ): UserTrustResult {
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId) val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
var masterKeyIsTrusted = false var masterKeyIsTrusted = false
var userKeyIsTrusted = false var userKeyIsTrusted = false
@ -334,7 +334,7 @@ internal class DefaultCrossSigningService @Inject constructor(
val checkSelfTrust = checkSelfTrust() val checkSelfTrust = checkSelfTrust()
if (checkSelfTrust.isVerified()) { if (checkSelfTrust.isVerified()) {
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey) cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey)
setUserKeysAsTrusted(userId, true) setUserKeysAsTrusted(myUserId, true)
} }
return checkSelfTrust return checkSelfTrust
} }
@ -355,7 +355,7 @@ internal class DefaultCrossSigningService @Inject constructor(
* . * .
*/ */
override fun isUserTrusted(otherUserId: String): Boolean { override fun isUserTrusted(otherUserId: String): Boolean {
return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true return cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
} }
override fun isCrossSigningVerified(): Boolean { override fun isCrossSigningVerified(): Boolean {
@ -367,7 +367,7 @@ internal class DefaultCrossSigningService @Inject constructor(
*/ */
override fun checkUserTrust(otherUserId: String): UserTrustResult { override fun checkUserTrust(otherUserId: String): UserTrustResult {
Timber.v("## CrossSigning checkUserTrust for $otherUserId") Timber.v("## CrossSigning checkUserTrust for $otherUserId")
if (otherUserId == userId) { if (otherUserId == myUserId) {
return checkSelfTrust() return checkSelfTrust()
} }
// I trust a user if I trust his master key // I trust a user if I trust his master key
@ -375,16 +375,14 @@ internal class DefaultCrossSigningService @Inject constructor(
// TODO what if the master key is signed by a device key that i have verified // TODO what if the master key is signed by a device key that i have verified
// First let's get my user key // First let's get my user key
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(myUserId)
checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId))
return UserTrustResult.Success
} }
fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult {
val myUserKey = myCrossSigningInfo?.userKey() val myUserKey = myCrossSigningInfo?.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId) ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
if (!myCrossSigningInfo.isTrusted()) { if (!myCrossSigningInfo.isTrusted()) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
@ -395,7 +393,7 @@ internal class DefaultCrossSigningService @Inject constructor(
?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") ?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "")
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
?.get(userId) // Signatures made by me ?.get(myUserId) // Signatures made by me
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
@ -421,9 +419,9 @@ internal class DefaultCrossSigningService @Inject constructor(
// Special case when it's me, // Special case when it's me,
// I have to check that MSK -> USK -> SSK // I have to check that MSK -> USK -> SSK
// and that MSK is trusted (i know the private key, or is signed by a trusted device) // and that MSK is trusted (i know the private key, or is signed by a trusted device)
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(myUserId)
return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(userId)) return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId))
} }
fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult { fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult {
@ -433,7 +431,7 @@ internal class DefaultCrossSigningService @Inject constructor(
// val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) // val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
val myMasterKey = myCrossSigningInfo?.masterKey() val myMasterKey = myCrossSigningInfo?.masterKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId) ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
// Is the master key trusted // Is the master key trusted
// 1) check if I know the private key // 1) check if I know the private key
@ -457,7 +455,7 @@ internal class DefaultCrossSigningService @Inject constructor(
olmPkSigning?.releaseSigning() olmPkSigning?.releaseSigning()
} else { } else {
// Maybe it's signed by a locally trusted device? // Maybe it's signed by a locally trusted device?
myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) ->
val potentialDeviceId = key.removePrefix("ed25519:") val potentialDeviceId = key.removePrefix("ed25519:")
val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId)
if (potentialDevice != null && potentialDevice.isVerified) { if (potentialDevice != null && potentialDevice.isVerified) {
@ -479,14 +477,14 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
val myUserKey = myCrossSigningInfo.userKey() val myUserKey = myCrossSigningInfo.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId) ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures
?.get(userId) // Signatures made by me ?.get(myUserId) // Signatures made by me
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK") Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK")
return UserTrustResult.KeyNotSigned(myUserKey) return UserTrustResult.KeyNotSigned(myUserKey)
} }
@ -502,14 +500,14 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
val mySSKey = myCrossSigningInfo.selfSigningKey() val mySSKey = myCrossSigningInfo.selfSigningKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId) ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures
?.get(userId) // Signatures made by me ?.get(myUserId) // Signatures made by me
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK") Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK")
return UserTrustResult.KeyNotSigned(mySSKey) return UserTrustResult.KeyNotSigned(mySSKey)
} }
@ -559,14 +557,14 @@ internal class DefaultCrossSigningService @Inject constructor(
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) { override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
Timber.d("## CrossSigning - Mark user $userId as trusted ") Timber.d("## CrossSigning - Mark user $otherUserId as trusted ")
// We should have this user keys // We should have this user keys
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
if (otherMasterKeys == null) { if (otherMasterKeys == null) {
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known"))
return@launch return@launch
} }
val myKeys = getUserCrossSigningKeys(userId) val myKeys = getUserCrossSigningKeys(myUserId)
if (myKeys == null) { if (myKeys == null) {
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
return@launch return@launch
@ -592,9 +590,9 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.setUserKeysAsTrusted(otherUserId, true) cryptoStore.setUserKeysAsTrusted(otherUserId, true)
// TODO update local copy with new signature directly here? kind of local echo of trust? // TODO update local copy with new signature directly here? kind of local echo of trust?
Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") Timber.d("## CrossSigning - Upload signature of $otherUserId MSK signed by USK")
val uploadQuery = UploadSignatureQueryBuilder() val uploadQuery = UploadSignatureQueryBuilder()
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) .withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature))
.build() .build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.executionThread = TaskThread.CRYPTO this.executionThread = TaskThread.CRYPTO
@ -608,20 +606,20 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.markMyMasterKeyAsLocallyTrusted(true) cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
checkSelfTrust() checkSelfTrust()
// re-verify all trusts // re-verify all trusts
onUsersDeviceUpdate(listOf(userId)) onUsersDeviceUpdate(listOf(myUserId))
} }
} }
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) { override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// This device should be yours // This device should be yours
val device = cryptoStore.getUserDevice(userId, deviceId) val device = cryptoStore.getUserDevice(myUserId, deviceId)
if (device == null) { if (device == null) {
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
return@launch return@launch
} }
val myKeys = getUserCrossSigningKeys(userId) val myKeys = getUserCrossSigningKeys(myUserId)
if (myKeys == null) { if (myKeys == null) {
callback.onFailure(Throwable("CrossSigning is not setup for this account")) callback.onFailure(Throwable("CrossSigning is not setup for this account"))
return@launch return@launch
@ -643,7 +641,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
val toUpload = device.copy( val toUpload = device.copy(
signatures = mapOf( signatures = mapOf(
userId myUserId
to to
mapOf( mapOf(
"ed25519:$ssPubKey" to newSignature "ed25519:$ssPubKey" to newSignature
@ -665,8 +663,8 @@ internal class DefaultCrossSigningService @Inject constructor(
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
?: return DeviceTrustResult.UnknownDevice(otherDeviceId) ?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
val myKeys = getUserCrossSigningKeys(userId) val myKeys = getUserCrossSigningKeys(myUserId)
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(myUserId))
if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys))
@ -721,7 +719,7 @@ internal class DefaultCrossSigningService @Inject constructor(
fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult { fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult {
val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified() val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified()
myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(myUserId))
if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys))
@ -809,7 +807,7 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
// If it's me, recheck trust of all users and devices? // If it's me, recheck trust of all users and devices?
val users = ArrayList<String>() val users = ArrayList<String>()
if (otherUserId == userId && currentTrust != trusted) { if (otherUserId == myUserId && currentTrust != trusted) {
// notify key requester // notify key requester
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted) outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
cryptoStore.updateUsersTrust { cryptoStore.updateUsersTrust {

View File

@ -161,6 +161,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
// i have all the new trusts, update DB // i have all the new trusts, update DB
trusts.forEach { trusts.forEach {
val verified = it.value?.isVerified() == true val verified = it.value?.isVerified() == true
Timber.v("[$myUserId] ## CrossSigning - Updating user trust: ${it.key} to $verified")
updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) updateCrossSigningKeysTrust(cryptoRealm, it.key, verified)
} }

View File

@ -289,6 +289,7 @@ dependencies {
testImplementation libs.tests.junit testImplementation libs.tests.junit
testImplementation libs.tests.kluent testImplementation libs.tests.kluent
testImplementation libs.mockk.mockk testImplementation libs.mockk.mockk
testImplementation libs.androidx.coreTesting
// Plant Timber tree for test // Plant Timber tree for test
testImplementation libs.tests.timberJunitRule testImplementation libs.tests.timberJunitRule
testImplementation libs.airbnb.mavericksTesting testImplementation libs.airbnb.mavericksTesting

View File

@ -37,6 +37,7 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -116,11 +117,22 @@ class RoomMemberListViewModel @AssistedInject constructor(
.map { deviceList -> .map { deviceList ->
// If any key change, emit the userIds list // If any key change, emit the userIds list
deviceList.groupBy { it.userId }.mapValues { deviceList.groupBy { it.userId }.mapValues {
val allDeviceTrusted = it.value.fold(it.value.isNotEmpty()) { prev, next -> getUserTrustLevel(it.key, it.value)
}
}
}
.execute { async ->
copy(trustLevelMap = async)
}
}
}
private fun getUserTrustLevel(userId: String, devices: List<CryptoDeviceInfo>): UserVerificationLevel {
val allDeviceTrusted = devices.fold(devices.isNotEmpty()) { prev, next ->
prev && next.trustLevel?.isCrossSigningVerified().orFalse() prev && next.trustLevel?.isCrossSigningVerified().orFalse()
} }
val mxCrossSigningInfo = session.cryptoService().crossSigningService().getUserCrossSigningKeys(it.key) val mxCrossSigningInfo = session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId)
when { return when {
mxCrossSigningInfo == null -> { mxCrossSigningInfo == null -> {
UserVerificationLevel.WAS_NEVER_VERIFIED UserVerificationLevel.WAS_NEVER_VERIFIED
} }
@ -137,13 +149,6 @@ class RoomMemberListViewModel @AssistedInject constructor(
} }
} }
} }
}
}
.execute { async ->
copy(trustLevelMap = async)
}
}
}
private fun observePowerLevel() { private fun observePowerLevel() {
PowerLevelsFlowFactory(room).createFlow() PowerLevelsFlowFactory(room).createFlow()

View File

@ -0,0 +1,282 @@
/*
* Copyright (c) 2022 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.app.features
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.members.RoomMemberListViewModel
import im.vector.app.features.roomprofile.members.RoomMemberListViewState
import im.vector.app.features.roomprofile.members.RoomMemberSummaryComparator
import im.vector.app.test.test
import im.vector.app.test.testCoroutineDispatchers
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
import org.matrix.android.sdk.api.session.room.members.MembershipService
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.session.user.UserService
import org.matrix.android.sdk.api.util.Optional
class MemberListViewModelTest {
@get:Rule
val mvrxTestRule = MvRxTestRule()
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
private val fakeRoomId = "!roomId"
private val args = RoomProfileArgs(fakeRoomId)
private val aliceMxid = "@alice:example.com"
private val bobMxid = "@bob:example.com"
private val marcMxid = "@marc:example.com"
private val aliceDevice1 = CryptoDeviceInfo(
deviceId = "ALICE_1",
userId = aliceMxid,
trustLevel = DeviceTrustLevel(true, true)
)
private val aliceDevice2 = CryptoDeviceInfo(
deviceId = "ALICE_2",
userId = aliceMxid,
trustLevel = DeviceTrustLevel(false, false)
)
private val bobDevice1 = CryptoDeviceInfo(
deviceId = "BOB_1",
userId = bobMxid,
trustLevel = DeviceTrustLevel(true, true)
)
private val bobDevice2 = CryptoDeviceInfo(
deviceId = "BOB_2",
userId = bobMxid,
trustLevel = DeviceTrustLevel(true, true)
)
private val markDevice = CryptoDeviceInfo(
deviceId = "MARK_1",
userId = marcMxid,
trustLevel = DeviceTrustLevel(false, true)
)
private val fakeMembershipservice: MembershipService = mockk {
val memberList = mutableListOf<RoomMemberSummary>(
RoomMemberSummary(Membership.JOIN, aliceMxid, displayName = "Alice"),
RoomMemberSummary(Membership.JOIN, bobMxid, displayName = "Bob"),
RoomMemberSummary(Membership.JOIN, marcMxid, displayName = "marc")
)
every { getRoomMembers(any()) } returns memberList
every { getRoomMembersLive(any()) } returns MutableLiveData(memberList)
every { areAllMembersLoadedLive() } returns MutableLiveData(true)
coEvery { areAllMembersLoaded() } returns true
}
private val fakeRoomCryptoService: RoomCryptoService = mockk {
every { isEncrypted() } returns true
}
private val fakeRoom: Room = mockk {
val fakeStateService: StateService = mockk {
every { getStateEventLive(any(), any()) } returns MutableLiveData()
every { getStateEventsLive(any(), any()) } returns MutableLiveData()
every { getStateEvent(any(), any()) } returns null
}
every { stateService() } returns fakeStateService
every { coroutineDispatchers } returns testCoroutineDispatchers
every { getRoomSummaryLive() } returns MutableLiveData<Optional<RoomSummary>>(Optional(fakeRoomSummary))
every { membershipService() } returns fakeMembershipservice
every { roomCryptoService() } returns fakeRoomCryptoService
every { roomSummary() } returns fakeRoomSummary
}
private val fakeUserService: UserService = mockk {
every { getIgnoredUsersLive() } returns MutableLiveData()
}
val fakeSession: Session = mockk {
val fakeCrossSigningService: CrossSigningService = mockk {
every { isUserTrusted(aliceMxid) } returns true
every { isUserTrusted(bobMxid) } returns true
every { isUserTrusted(marcMxid) } returns false
every { getUserCrossSigningKeys(aliceMxid) } returns MXCrossSigningInfo(
aliceMxid,
crossSigningKeys = listOf(
CryptoCrossSigningKey(
aliceMxid,
usages = listOf("master"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(true, true),
signatures = emptyMap()
),
CryptoCrossSigningKey(
aliceMxid,
usages = listOf("self_signing"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(true, true),
signatures = emptyMap()
),
CryptoCrossSigningKey(
aliceMxid,
usages = listOf("user_signing"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(true, true),
signatures = emptyMap()
)
),
true
)
every { getUserCrossSigningKeys(bobMxid) } returns MXCrossSigningInfo(
aliceMxid,
crossSigningKeys = listOf(
CryptoCrossSigningKey(
bobMxid,
usages = listOf("master"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(true, true),
signatures = emptyMap()
),
CryptoCrossSigningKey(
bobMxid,
usages = listOf("self_signing"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(true, true),
signatures = emptyMap()
),
CryptoCrossSigningKey(
bobMxid,
usages = listOf("user_signing"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(true, true),
signatures = emptyMap()
)
),
true
)
every { getUserCrossSigningKeys(marcMxid) } returns MXCrossSigningInfo(
aliceMxid,
crossSigningKeys = listOf(
CryptoCrossSigningKey(
marcMxid,
usages = listOf("master"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(false, false),
signatures = emptyMap()
),
CryptoCrossSigningKey(
marcMxid,
usages = listOf("self_signing"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(false, false),
signatures = emptyMap()
),
CryptoCrossSigningKey(
marcMxid,
usages = listOf("user_signing"),
keys = emptyMap(),
trustLevel = DeviceTrustLevel(false, false),
signatures = emptyMap()
)
),
true
)
}
val fakeCryptoService: CryptoService = mockk {
every { crossSigningService() } returns fakeCrossSigningService
every {
getLiveCryptoDeviceInfo(listOf(aliceMxid, bobMxid, marcMxid))
} returns MutableLiveData(
listOf(
aliceDevice1, aliceDevice2, bobDevice1, bobDevice2, markDevice
)
)
}
val fakeRoomService: RoomService = mockk {
every { getRoom(any()) } returns fakeRoom
}
every { roomService() } returns fakeRoomService
every { userService() } returns fakeUserService
every { cryptoService() } returns fakeCryptoService
}
private val fakeRoomSummary = RoomSummary(
roomId = fakeRoomId,
displayName = "Fake Room",
topic = "A topic",
isEncrypted = true,
encryptionEventTs = 0,
typingUsers = emptyList(),
)
@Test
fun testBasicUserVerificationLevels() {
val viewModel = createViewModel()
viewModel
.test()
.assertPredicateLatestState {
val trustMap = it.trustLevelMap.invoke() ?: return@assertPredicateLatestState false
trustMap[aliceMxid] == UserVerificationLevel.VERIFIED_WITH_DEVICES_UNTRUSTED &&
trustMap[bobMxid] == UserVerificationLevel.VERIFIED_ALL_DEVICES_TRUSTED &&
trustMap[marcMxid] == UserVerificationLevel.UNVERIFIED_BUT_WAS_PREVIOUSLY
}
.finish()
}
private fun createViewModel(): RoomMemberListViewModel {
return RoomMemberListViewModel(
RoomMemberListViewState(args),
RoomMemberSummaryComparator(),
fakeSession,
)
}
}