Merge pull request #7 from poljar/feature/fga/suspend_api

Cleaning up some code and adding more suspend (removing most runBlocking)
This commit is contained in:
ganfra 2022-05-12 12:06:42 +02:00 committed by GitHub
commit 559404f953
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 2127 additions and 2123 deletions

View file

@ -125,21 +125,21 @@ class FlowSession(private val session: Session) {
}
fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> {
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow()
return session.cryptoService().getLiveCryptoDeviceInfoList(userId)
.startWith(session.coroutineDispatchers.io) {
session.cryptoService().getCryptoDeviceInfo(userId)
session.cryptoService().getCryptoDeviceInfoList(userId)
}
}
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow()
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId)
.startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
}
}
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow()
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys()
.startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
}

View file

@ -110,6 +110,9 @@ dependencies {
implementation libs.jetbrains.coroutinesAndroid
implementation 'org.matrix.rustcomponents:crypto-android:0.1.1-SNAPSHOT'
//implementation files('libs/crypto-android-release.aar')
// implementation(name: 'crypto-android-release', ext: 'aar')
implementation 'net.java.dev.jna:jna:5.10.0@aar'
implementation libs.androidx.appCompat

Binary file not shown.

View file

@ -378,7 +378,7 @@ class CommonTestHelper(context: Context) {
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
}
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: suspend (() -> Boolean)) {
while (true) {
delay(1000)
if (condition()) {

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.common
import android.os.SystemClock
import android.util.Log
import androidx.lifecycle.Observer
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@ -29,8 +30,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.Event
@ -275,7 +275,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
}
fun initializeCrossSigning(session: Session) {
testHelper.doSync<Unit> {
testHelper.runBlockingTest {
session.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -288,7 +288,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
)
)
}
}, it)
})
}
}
@ -300,24 +300,24 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val aliceVerificationService = alice.cryptoService().verificationService()
val bobVerificationService = bob.cryptoService().verificationService()
aliceVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID,
roomId,
bob.myUserId,
bob.sessionParams.credentials.deviceId!!)
runBlocking {
aliceVerificationService.beginKeyVerification(
VerificationMethod.SAS,
roomId,
bob.myUserId,)
}
// we should reach SHOW SAS on both
var alicePovTx: OutgoingSasVerificationTransaction? = null
var bobPovTx: IncomingSasVerificationTransaction? = null
var alicePovTx: SasVerificationTransaction? = null
var bobPovTx: SasVerificationTransaction? = null
// wait for alice to get the ready
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
bobPovTx?.performAccept()
bobPovTx?.acceptVerification()
true
} else {
false
@ -327,18 +327,18 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is ${alicePovTx?.state}")
alicePovTx?.state == VerificationTxState.ShortCodeReady
}
}
// wait for alice to get the ready
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
bobPovTx?.performAccept()
bobPovTx?.acceptVerification()
}
bobPovTx?.state == VerificationTxState.ShortCodeReady
}
@ -346,8 +346,10 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
bobPovTx!!.userHasVerifiedShortCode()
alicePovTx!!.userHasVerifiedShortCode()
runBlocking {
bobPovTx!!.userHasVerifiedShortCode()
alicePovTx!!.userHasVerifiedShortCode()
}
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {

View file

@ -60,8 +60,8 @@ class PreShareKeysTest : InstrumentedTest {
Log.d("#Test", "Room Key Received from alice $preShareCount")
// Force presharing of new outbound key
testHelper.doSync<Unit> {
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
testHelper.runBlockingTest {
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
}
testHelper.waitWithLatch { latch ->

View file

@ -144,9 +144,12 @@ class UnwedgingTest : InstrumentedTest {
// - Store the olm session between A&B devices
// Let us pickle our session with bob here so we can later unpickle it
// and wedge our session.
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
var myDevice = testHelper.runBlockingTest {
bobSession.cryptoService().getMyCryptoDevice()
}
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(myDevice.identityKey()!!)
sessionIdsForBob!!.size shouldBe 1
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), myDevice.identityKey()!!)!!
val oldSession = serializeForRealm(olmSession.olmSession)
@ -174,7 +177,10 @@ class UnwedgingTest : InstrumentedTest {
// Let us wedge the session now. Set crypto state like after the first message
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
myDevice = testHelper.runBlockingTest {
bobSession.cryptoService().getMyCryptoDevice()
}
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), myDevice.identityKey()!!)
Thread.sleep(6_000)
// Force new session, and key share
@ -207,7 +213,7 @@ class UnwedgingTest : InstrumentedTest {
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
// It's a trick to force key request on fail to decrypt
testHelper.doSync<Unit> {
testHelper.runBlockingTest {
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -220,7 +226,7 @@ class UnwedgingTest : InstrumentedTest {
)
)
}
}, it)
})
}
// Wait until we received back the key

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@ -53,7 +54,7 @@ class XSigningTest : InstrumentedTest {
fun test_InitializeAndStoreKeys() {
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
testHelper.doSync<Unit> {
testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
@ -65,10 +66,12 @@ class XSigningTest : InstrumentedTest {
)
)
}
}, it)
})
}
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
val myCrossSigningKeys = testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
}
val masterPubKey = myCrossSigningKeys?.masterKey()
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
@ -78,7 +81,10 @@ class XSigningTest : InstrumentedTest {
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
val userTrustResult = testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId)
}
assertTrue("Signing Keys should be trusted", userTrustResult.isVerified())
testHelper.signOutAndClose(aliceSession)
}
@ -99,29 +105,37 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD
)
testHelper.doSync<Unit> {
testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams)
}
}, it)
})
}
testHelper.runBlockingTest {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
})
}
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
}, it) }
// Check that alice can see bob keys
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true) }
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
val bobKeysFromAlicePOV = testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
}
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
val myKeys = testHelper.runBlockingTest {
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
}
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
@ -145,25 +159,33 @@ class XSigningTest : InstrumentedTest {
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) }
testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams)
}
})
}
testHelper.runBlockingTest {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
})
}
// Check that alice can see bob keys
val bobUserId = bobSession.myUserId
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) }
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
val bobKeysFromAlicePOV = testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
}
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().trustUser(bobUserId)
}
// Now bobs logs in on a new device and verifies it
// We will want to test that in alice POV, this new device would be trusted by cross signing
@ -180,7 +202,9 @@ class XSigningTest : InstrumentedTest {
fail("Bob should see the new device")
}
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
val bobSecondDevicePOVFirstDevice = runBlocking {
bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId)
}
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session
@ -198,7 +222,9 @@ class XSigningTest : InstrumentedTest {
fail("Alice should see the new device")
}
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
val result = testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
}
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
testHelper.signOutAndClose(aliceSession)

View file

@ -23,6 +23,7 @@ import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.fail
import kotlinx.coroutines.delay
import org.junit.Assert
import org.junit.FixMethodOrder
import org.junit.Ignore
@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -50,7 +50,6 @@ import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -93,7 +92,9 @@ class KeyShareTests : InstrumentedTest {
assert(receivedEvent!!.isEncrypted())
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
fail("should fail")
} catch (failure: Throwable) {
}
@ -106,7 +107,7 @@ class KeyShareTests : InstrumentedTest {
var outGoingRequestId: String? = null
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
.filter { req ->
@ -148,14 +149,17 @@ class KeyShareTests : InstrumentedTest {
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
fail("should fail")
} catch (failure: Throwable) {
}
// Mark the device as trusted
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession2.sessionParams.deviceId ?: "")
commonTestHelper.runBlockingTest {
aliceSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession2.sessionParams.deviceId ?: "")
}
// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
@ -185,7 +189,9 @@ class KeyShareTests : InstrumentedTest {
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
commonTestHelper.runBlockingTest {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
} catch (failure: Throwable) {
fail("should have been able to decrypt")
}
@ -199,7 +205,7 @@ class KeyShareTests : InstrumentedTest {
fun test_ShareSSSSSecret() {
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
commonTestHelper.doSync<Unit> {
commonTestHelper.runBlockingTest {
aliceSession1.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -211,7 +217,7 @@ class KeyShareTests : InstrumentedTest {
)
)
}
}, it)
})
}
// Also bootstrap keybackup on first session
@ -242,27 +248,30 @@ class KeyShareTests : InstrumentedTest {
aliceVerificationService1.addListener(object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx !is SasVerificationTransaction) return
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
if (tx is SasVerificationTransaction) {
if (tx.state == VerificationTxState.OnStarted) {
(tx as IncomingSasVerificationTransaction).performAccept()
when (tx.state) {
VerificationTxState.OnStarted -> commonTestHelper.runBlockingTest {
tx.acceptVerification()
}
if (tx.state == VerificationTxState.ShortCodeReady) {
VerificationTxState.ShortCodeReady -> commonTestHelper.runBlockingTest {
session1ShortCode = tx.getDecimalCodeRepresentation()
Thread.sleep(500)
delay(500)
tx.userHasVerifiedShortCode()
}
}
}
})
}
)
aliceVerificationService2.addListener(object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx !is SasVerificationTransaction) return
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
if (tx is SasVerificationTransaction) {
if (tx.state == VerificationTxState.ShortCodeReady) {
when (tx.state) {
VerificationTxState.ShortCodeReady -> commonTestHelper.runBlockingTest {
session2ShortCode = tx.getDecimalCodeRepresentation()
Thread.sleep(500)
delay(500)
tx.userHasVerifiedShortCode()
}
}
@ -270,12 +279,13 @@ class KeyShareTests : InstrumentedTest {
})
val txId = "m.testVerif12"
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
?: "", txId)
commonTestHelper.runBlockingTest {
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, txId)
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
aliceSession1.cryptoService().getCryptoDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
}
}
@ -312,7 +322,7 @@ class KeyShareTests : InstrumentedTest {
fun test_ImproperKeyShareBug() {
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
commonTestHelper.doSync<Unit> {
commonTestHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -325,7 +335,7 @@ class KeyShareTests : InstrumentedTest {
)
)
}
}, it)
})
}
// Create an encrypted room and send a couple of messages
@ -346,7 +356,7 @@ class KeyShareTests : InstrumentedTest {
// Create bob session
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
commonTestHelper.doSync<Unit> {
commonTestHelper.runBlockingTest {
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -359,7 +369,7 @@ class KeyShareTests : InstrumentedTest {
)
)
}
}, it)
})
}
// Let alice invite bob
@ -380,7 +390,10 @@ class KeyShareTests : InstrumentedTest {
val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
var dRes =
commonTestHelper.runBlockingTest {
tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
}
assert(dRes == null)
@ -391,7 +404,9 @@ class KeyShareTests : InstrumentedTest {
Thread.sleep(3_000)
// With the bug the first session would have improperly reshare that key :/
dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
dRes = commonTestHelper.runBlockingTest {
tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
}
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
assert(dRes?.clearEvent == null)
}

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.gossiping
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.FixMethodOrder
import org.junit.Ignore
@ -92,7 +93,9 @@ class WithHeldTests : InstrumentedTest {
// Bob should not be able to decrypt because the keys is withheld
try {
// .. might need to wait a bit for stability?
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
runBlocking {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
}
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@ -117,7 +120,9 @@ class WithHeldTests : InstrumentedTest {
// Previous message should still be undecryptable (partially withheld session)
try {
// .. might need to wait a bit for stability?
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
runBlocking {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
}
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@ -164,7 +169,9 @@ class WithHeldTests : InstrumentedTest {
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
try {
// .. might need to wait a bit for stability?
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
runBlocking {
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
}
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@ -222,7 +229,7 @@ class WithHeldTests : InstrumentedTest {
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
// Trust bob second device from Alice POV
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!)
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!)
}

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@ -38,7 +39,6 @@ import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import java.util.concurrent.CountDownLatch
@ -164,17 +164,22 @@ class KeysBackupTest : InstrumentedTest {
val latch = CountDownLatch(1)
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
runBlocking {
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
}
val stateObserver = StateObserver(keysBackup, latch, 5)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
testHelper.await(latch)
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
val nbOfKeys = runBlocking {
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
}
val backedUpKeys = runBlocking {
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
}
assertEquals(2, nbOfKeys)
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
@ -833,9 +838,12 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(1, keysBackupVersionTrust.signatures.size)
val signature = keysBackupVersionTrust.signatures[0]
val device = runBlocking {
cryptoTestData.firstSession.cryptoService().getMyCryptoDevice()
}
assertTrue(signature.valid)
assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
assertEquals(device.deviceId, signature.deviceId)
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null)
@ -1056,7 +1064,9 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup2.isEnabled)
// - Validate the old device from the new one
aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId)
testHelper.runBlockingTest {
aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
}
// -> Backup should automatically enable on the new device
val latch4 = CountDownLatch(1)

View file

@ -80,7 +80,10 @@ class KeysBackupTestHelper(
}
}
Assert.assertEquals(2, cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys())
val totalNumbersOfBackedUpKeys = testHelper.runBlockingTest {
cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
}
Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
val aliceUserId = cryptoTestData.firstSession.myUserId
@ -88,15 +91,21 @@ class KeysBackupTestHelper(
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
// Test check: aliceSession2 has no keys at login
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
val inboundGroupSessionCount = testHelper.runBlockingTest {
aliceSession2.cryptoService().inboundGroupSessionsCount(false)
}
Assert.assertEquals(0, inboundGroupSessionCount)
// Wait for backup state to be NotTrusted
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
stateObserver.stopAndCheckStates(null)
val totalNumbersOfBackedUpKeysFromNewSession = testHelper.runBlockingTest {
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
}
return KeysBackupScenarioData(cryptoTestData,
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys(),
totalNumbersOfBackedUpKeysFromNewSession,
prepareKeysBackupDataResult,
aliceSession2)
}
@ -182,7 +191,10 @@ class KeysBackupTestHelper(
Assert.assertEquals(total, imported)
// - The new device must have the same count of megolm keys
Assert.assertEquals(testData.aliceKeys, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
val inboundGroupSessionCount = testHelper.runBlockingTest {
testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)
}
Assert.assertEquals(testData.aliceKeys, inboundGroupSessionCount)
// - Alice must have the same keys on both devices
//

View file

@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.crypto.verification
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder
@ -32,9 +32,7 @@ import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -49,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import timber.log.Timber
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@ -75,10 +74,13 @@ class SASTest : InstrumentedTest {
}
bobVerificationService.addListener(bobListener)
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
bobSession.myUserId,
bobSession.cryptoService().getMyDevice().deviceId,
null)
val bobDevice = testHelper.runBlockingTest {
bobSession.cryptoService().getMyCryptoDevice()
}
val txID = testHelper.runBlockingTest {
aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), forceDownload = true)
aliceVerificationService.beginDeviceVerification(bobSession.myUserId, bobDevice.deviceId)
}
assertNotNull("Alice should have a started transaction", txID)
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
@ -90,16 +92,13 @@ class SASTest : InstrumentedTest {
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
assertTrue(bobKeyTx is SasVerificationTransaction)
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
assertTrue(aliceKeyTx is SasVerificationTransaction)
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.state)
// Let's cancel from alice side
val cancelLatch = CountDownLatch(1)
@ -107,7 +106,7 @@ class SASTest : InstrumentedTest {
val bobListener2 = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == txID) {
val immutableState = (tx as SASDefaultVerificationTransaction).state
val immutableState = (tx as SasVerificationTransaction).state
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
cancelLatch.countDown()
}
@ -116,14 +115,16 @@ class SASTest : InstrumentedTest {
}
bobVerificationService.addListener(bobListener2)
aliceSasTx.cancel(CancelCode.User)
testHelper.runBlockingTest {
aliceKeyTx.cancel(CancelCode.User)
}
testHelper.await(cancelLatch)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on alice side", aliceKeyTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobKeyTx.state is VerificationTxState.Cancelled)
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
val bobCancelState = bobKeyTx.state as VerificationTxState.Cancelled
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
@ -131,9 +132,6 @@ class SASTest : InstrumentedTest {
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
cryptoTestData.cleanUp(testHelper)
}
@ -177,12 +175,16 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
val aliceDevice = testHelper.runBlockingTest {
aliceSession.cryptoService().getMyCryptoDevice().deviceId
}
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
(tx as IncomingSasVerificationTransaction).performAccept()
if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
testHelper.runBlockingTest {
tx.acceptVerification()
}
}
}
}
@ -226,7 +228,9 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
val aliceDevice = testHelper.runBlockingTest {
aliceSession.cryptoService().getMyCryptoDevice().deviceId
}
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
@ -267,7 +271,9 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
val aliceDevice = testHelper.runBlockingTest {
aliceSession.cryptoService().getMyCryptoDevice().deviceId
}
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
@ -283,12 +289,15 @@ class SASTest : InstrumentedTest {
aliceUserID: String?,
aliceDevice: String?,
tid: String,
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
protocols: List<String> = emptyList(),
hashes: List<String> = emptyList(),
mac: List<String> = emptyList(),
codes: List<String> = emptyList()) {
val deviceId = runBlocking {
bobSession.cryptoService().getMyCryptoDevice().deviceId
}
val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
fromDevice = deviceId,
method = VerificationMethod.SAS.toValue(),
transactionId = tid,
keyAgreementProtocols = protocols,
@ -323,16 +332,16 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2)
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
val aliceCancelledLatch = CountDownLatch(1)
val createdTx = mutableListOf<VerificationTransaction>()
val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx as SASDefaultVerificationTransaction)
createdTx.add(tx)
aliceCreatedLatch.countDown()
}
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
if (tx.state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
aliceCancelledLatch.countDown()
}
}
@ -340,10 +349,14 @@ class SASTest : InstrumentedTest {
aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
val bobDeviceId = testHelper.runBlockingTest {
bobSession.cryptoService().getMyCryptoDevice().deviceId
}
testHelper.runBlockingTest {
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), forceDownload = true)
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
}
testHelper.await(aliceCreatedLatch)
testHelper.await(aliceCancelledLatch)
@ -366,17 +379,10 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
var accepted: ValidVerificationInfoAccept? = null
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
val aliceAcceptedLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
val at = tx as SASDefaultVerificationTransaction
accepted = at.accepted
startReq = at.startReq
if (tx.state is VerificationTxState.OnAccepted) {
aliceAcceptedLatch.countDown()
}
}
@ -385,90 +391,62 @@ class SASTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
bobVerificationService.removeListener(this)
val at = tx as IncomingSasVerificationTransaction
at.performAccept()
testHelper.runBlockingTest {
tx.acceptVerification()
}
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceAcceptedLatch)
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
// check that agreement is valid
assertTrue("Agreed Protocol should be Valid", accepted != null)
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
accepted!!.shortAuthenticationStrings.forEach {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
val bobDeviceId = runBlocking {
bobSession.cryptoService().getMyCryptoDevice().deviceId
}
testHelper.runBlockingTest {
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
}
testHelper.await(aliceAcceptedLatch)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_aliceAndBobSASCode() {
val supportedMethods = listOf(VerificationMethod.SAS)
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val bobSession = cryptoTestData.secondSession!!
val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1)
val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
aliceSASLatch.countDown()
}
else -> Unit
}
Timber.v("Alice transactionUpdated: ${tx.state}")
latch.countDown()
}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
aliceSession.cryptoService().verificationService().addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
}
else -> Unit
}
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
bobSASLatch.countDown()
}
Timber.v("Bob transactionUpdated: ${tx.state}")
latch.countDown()
}
}
bobVerificationService.addListener(bobListener)
bobSession.cryptoService().verificationService().addListener(bobListener)
testHelper.runBlockingTest {
aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
}
testHelper.await(latch)
val aliceTx = aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
cryptoTestData.cleanUp(testHelper)
}
@ -478,7 +456,8 @@ class SASTest : InstrumentedTest {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -487,21 +466,26 @@ class SASTest : InstrumentedTest {
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated pr=$pr")
}
var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
Log.v("TEST", "== aliceState ${uxState.name}")
when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
Timber.v("Alice transactionUpdated: ${tx.state}")
if (tx !is SasVerificationTransaction) return
when (tx.state) {
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
tx.userHasVerifiedShortCode()
}
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
VerificationTxState.Verified -> {
if (matchOnce) {
matchOnce = false
aliceSASLatch.countDown()
}
}
else -> Unit
else -> Unit
}
}
}
@ -511,41 +495,53 @@ class SASTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener {
var acceptOnce = true
var matchOnce = true
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated: pr=$pr")
}
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
Log.v("TEST", "== bobState ${uxState.name}")
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
Timber.v("Bob transactionUpdated: ${tx.state}")
if (tx !is SasVerificationTransaction) return
when (tx.state) {
VerificationTxState.OnStarted -> testHelper.runBlockingTest {
if (acceptOnce) {
acceptOnce = false
tx.performAccept()
tx.acceptVerification()
}
}
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
if (matchOnce) {
matchOnce = false
tx.userHasVerifiedShortCode()
}
}
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
VerificationTxState.ShortCodeAccepted -> {
bobSASLatch.countDown()
}
else -> Unit
else -> Unit
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
val bobDeviceId = runBlocking {
bobSession.cryptoService().getMyCryptoDevice().deviceId
}
testHelper.runBlockingTest {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
}
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
}
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
}
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.cleanUp(testHelper)
@ -563,11 +559,13 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val req = aliceVerificationService.requestKeyVerificationInDMs(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
bobSession.myUserId,
cryptoTestData.roomId
)
val req = testHelper.runBlockingTest {
aliceVerificationService.requestKeyVerificationInDMs(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
bobSession.myUserId,
cryptoTestData.roomId
)
}
var requestID: String? = null
@ -590,11 +588,13 @@ class SASTest : InstrumentedTest {
}
}
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId,
requestID!!
)
testHelper.runBlockingTest {
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId,
requestID!!
)
}
// wait for alice to get the ready
testHelper.waitWithLatch {
@ -606,19 +606,19 @@ class SASTest : InstrumentedTest {
}
// Start concurrent!
aliceVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
bobSession.myUserId,
bobSession.sessionParams.deviceId!!)
testHelper.runBlockingTest {
aliceVerificationService.requestKeyVerificationInDMs(
methods = listOf(VerificationMethod.SAS),
otherUserId = bobSession.myUserId,
roomId = cryptoTestData.roomId
)
bobVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
aliceSession.myUserId,
aliceSession.sessionParams.deviceId!!)
bobVerificationService.requestKeyVerificationInDMs(
methods = listOf(VerificationMethod.SAS),
otherUserId = aliceSession.myUserId,
roomId = cryptoTestData.roomId
)
}
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction?

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData
import org.matrix.android.sdk.common.CryptoTestHelper
import timber.log.Timber
import java.util.concurrent.CountDownLatch
class SasVerificationTestHelper(private val testHelper: CommonTestHelper, private val cryptoTestHelper: CryptoTestHelper) {
fun requestVerificationAndWaitForReadyState(cryptoTestData: CryptoTestData, supportedMethods: List<VerificationMethod>): String {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
cryptoTestHelper.initializeCrossSigning(aliceSession)
cryptoTestHelper.initializeCrossSigning(bobSession)
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request
Timber.v("Alice request updated: $pr")
if (pr.isReady) {
latch.countDown()
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request
Timber.v("Bob accepts the verification request")
testHelper.runBlockingTest {
bobVerificationService.readyPendingVerification(
supportedMethods,
aliceSession.myUserId,
pr.transactionId!!
)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready
Timber.v("Bob request updated $pr")
if (pr.isReady) {
bobReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
testHelper.runBlockingTest {
aliceVerificationService.requestKeyVerificationInDMs(supportedMethods, bobUserId, cryptoTestData.roomId)
}
testHelper.await(latch)
bobVerificationService.removeListener(bobListener)
aliceVerificationService.removeListener(aliceListener)
return bobReadyPendingVerificationRequest?.transactionId!!
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification.qrcode
package org.matrix.android.sdk.internal.crypto.verification
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
@ -23,19 +23,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.TestConstants
import timber.log.Timber
import java.util.concurrent.CountDownLatch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@ -147,50 +141,19 @@ class VerificationTest : InstrumentedTest {
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
)
// TODO Add tests without SAS
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult) {
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
testHelper.doSync<Unit> { callback ->
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD,
session = flowResponse.session
)
)
}
}, callback)
}
testHelper.doSync<Unit> { callback ->
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
user = bobSession.myUserId,
password = TestConstants.PASSWORD,
session = flowResponse.session
)
)
}
}, callback)
}
cryptoTestHelper.initializeCrossSigning(aliceSession)
cryptoTestHelper.initializeCrossSigning(bobSession)
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
@ -202,6 +165,7 @@ class VerificationTest : InstrumentedTest {
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request
Timber.v("Alice is ready: ${pr.isReady}")
if (pr.isReady) {
aliceReadyPendingVerificationRequest = pr
latch.countDown()
@ -213,16 +177,19 @@ class VerificationTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request
bobVerificationService.readyPendingVerificationInDMs(
bobSupportedMethods,
aliceSession.myUserId,
cryptoTestData.roomId,
pr.transactionId!!
)
Timber.v("Bob accepts the verification request")
testHelper.runBlockingTest {
bobVerificationService.readyPendingVerification(
bobSupportedMethods,
aliceSession.myUserId,
pr.transactionId!!
)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready
Timber.v("Bob is ready: ${pr.isReady}")
if (pr.isReady) {
bobReadyPendingVerificationRequest = pr
latch.countDown()
@ -233,7 +200,9 @@ class VerificationTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
testHelper.runBlockingTest {
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
}
testHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr ->

View file

@ -1,46 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldNotBeEqualTo
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class SharedSecretTest : InstrumentedTest {
@Test
fun testSharedSecretLengthCase() {
repeat(100) {
generateSharedSecretV2().length shouldBe 11
}
}
@Test
fun testSharedDiffCase() {
val sharedSecret1 = generateSharedSecretV2()
val sharedSecret2 = generateSharedSecretV2()
sharedSecret1 shouldNotBeEqualTo sharedSecret2
}
}

View file

@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
@ -32,14 +32,12 @@ import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.NewSessionListener
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
interface CryptoService {
@ -49,9 +47,9 @@ interface CryptoService {
fun keysBackupService(): KeysBackupService
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
suspend fun setDeviceName(deviceId: String, deviceName: String)
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
fun getCryptoVersion(context: Context, longFormat: Boolean): String
@ -61,11 +59,9 @@ interface CryptoService {
fun setWarnOnUnknownDevices(warn: Boolean)
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
fun getMyDevice(): CryptoDeviceInfo
suspend fun getMyCryptoDevice(): CryptoDeviceInfo
fun getGlobalBlacklistUnverifiedDevices(): Boolean
@ -73,8 +69,6 @@ interface CryptoService {
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
fun getDeviceTrackingStatus(userId: String): Int
suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
password: String,
progressListener: ProgressListener?): ImportRoomKeysResult
@ -83,7 +77,7 @@ interface CryptoService {
fun setRoomBlacklistUnverifiedDevices(roomId: String)
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun reRequestRoomKeyForEvent(event: Event)
@ -91,29 +85,26 @@ interface CryptoService {
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
suspend fun fetchDevicesList(): List<DeviceInfo>
fun getMyDevicesInfo(): List<DeviceInfo>
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
fun isRoomEncrypted(roomId: String): Boolean
fun encryptEventContent(eventContent: Content,
suspend fun encryptEventContent(eventContent: Content,
eventType: String,
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>)
roomId: String): MXEncryptEventContentResult
fun discardOutboundSession(roomId: String)
@Throws(MXCryptoError::class)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun getEncryptionAlgorithm(roomId: String): String?
@ -121,11 +112,11 @@ interface CryptoService {
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo>
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo>
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener)
@ -150,7 +141,7 @@ interface CryptoService {
* Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message.
*/
fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>)
suspend fun prepareToEncrypt(roomId: String)
/**
* When LL all room members might not be loaded when setting up encryption.

View file

@ -16,8 +16,7 @@
package org.matrix.android.sdk.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.Optional
@ -29,31 +28,30 @@ interface CrossSigningService {
/**
* Is our own device signed by our own cross signing identity
*/
fun isCrossSigningVerified(): Boolean
suspend fun isCrossSigningVerified(): Boolean
// TODO this isn't used anywhere besides in tests?
// Is this the local trust concept that we have for devices?
fun isUserTrusted(otherUserId: String): Boolean
suspend fun isUserTrusted(otherUserId: String): Boolean
/**
* Will not force a download of the key, but will verify signatures trust chain.
* Checks that my trusted user key has signed the other user UserKey
*/
fun checkUserTrust(otherUserId: String): UserTrustResult
suspend fun checkUserTrust(otherUserId: String): UserTrustResult
/**
* Initialize cross signing for this user.
* Users needs to enter credentials
*/
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?,
callback: MatrixCallback<Unit>)
suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
/**
* Does our own user have a valid cross signing identity uploaded.
*
* In other words has any of our devices uploaded public cross signing keys to the server.
*/
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
suspend fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
/**
* Inject the private cross signing keys, likely from backup, into our store.
@ -62,25 +60,25 @@ interface CrossSigningService {
* by the server and if they do so
*/
suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?,
sskPrivateKey: String?): UserTrustResult
uskKeyPrivateKey: String?,
sskPrivateKey: String?): UserTrustResult
/**
* Get the public cross signing keys for the given user
*
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
*/
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>>
/** Get our own public cross signing keys */
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
/** Get our own private cross signing keys */
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>>
/**
* Can we sign our other devices or other users?
@ -93,11 +91,10 @@ interface CrossSigningService {
fun allPrivateKeysKnown(): Boolean
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
fun trustUser(otherUserId: String,
callback: MatrixCallback<Unit>)
suspend fun trustUser(otherUserId: String)
/** Mark our own master key as trusted */
fun markMyMasterKeyAsTrusted()
suspend fun markMyMasterKeyAsTrusted()
/**
* Sign one of your devices and upload the signature
@ -114,10 +111,10 @@ interface CrossSigningService {
* using the self-signing key for our own devices or using the user-signing key and the master
* key of another user.
*/
fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
// TODO what is locallyTrusted used for?
locallyTrusted: Boolean?): DeviceTrustResult
suspend fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
// TODO what is locallyTrusted used for?
locallyTrusted: Boolean?): DeviceTrustResult
// FIXME Those method do not have to be in the service
// TODO those three methods doesn't seem to be used anywhere?

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
interface KeysBackupService {
/**
* Retrieve the current version of the backup from the homeserver
*
@ -45,12 +46,12 @@ interface KeysBackupService {
/**
* Facility method to get the total number of locally stored keys
*/
fun getTotalNumbersOfKeys(): Int
suspend fun getTotalNumbersOfKeys(): Int
/**
* Facility method to get the number of backed up keys
*/
fun getTotalNumbersOfBackedUpKeys(): Int
suspend fun getTotalNumbersOfBackedUpKeys(): Int
// /**
// * Start to back up keys immediately.
@ -71,7 +72,7 @@ interface KeysBackupService {
/**
* Return the current progress of the backup
*/
fun getBackupProgress(progressListener: ProgressListener)
suspend fun getBackupProgress(progressListener: ProgressListener)
/**
* Get information about a backup version defined on the homeserver.
@ -128,7 +129,7 @@ interface KeysBackupService {
* Ask if the backup on the server contains keys that we may do not have locally.
* This should be called when entering in the state READY_TO_BACKUP
*/
fun canRestoreKeys(): Boolean
suspend fun canRestoreKeys(): Boolean
/**
* Set trust on a keys backup version.
@ -199,7 +200,7 @@ interface KeysBackupService {
// For gossiping
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
}

View file

@ -1,34 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.crypto.verification
interface IncomingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState
fun performAccept()
enum class UxState {
UNKNOWN,
SHOW_ACCEPT,
WAIT_FOR_KEY_AGREEMENT,
SHOW_SAS,
WAIT_FOR_VERIFICATION,
VERIFIED,
CANCELLED_BY_ME,
CANCELLED_BY_OTHER
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.crypto.verification
interface OutgoingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState
enum class UxState {
UNKNOWN,
WAIT_FOR_START,
WAIT_FOR_KEY_AGREEMENT,
SHOW_SAS,
WAIT_FOR_VERIFICATION,
VERIFIED,
CANCELLED_BY_ME,
CANCELLED_BY_OTHER
}
}

View file

@ -26,15 +26,15 @@ interface QrCodeVerificationTransaction : VerificationTransaction {
/**
* Call when you have scan the other user QR code
*/
fun userHasScannedOtherQrCode(otherQrCodeText: String)
suspend fun userHasScannedOtherQrCode(otherQrCodeText: String)
/**
* Call when you confirm that other user has scanned your QR code
*/
fun otherUserScannedMyQrCode()
suspend fun otherUserScannedMyQrCode()
/**
* Call when you do not confirm that other user has scanned your QR code
*/
fun otherUserDidNotScannedMyQrCode()
suspend fun otherUserDidNotScannedMyQrCode()
}

View file

@ -20,8 +20,6 @@ interface SasVerificationTransaction : VerificationTransaction {
fun supportsEmoji(): Boolean
fun supportsDecimal(): Boolean
fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
fun getDecimalCodeRepresentation(): String
@ -30,9 +28,9 @@ interface SasVerificationTransaction : VerificationTransaction {
* To be called by the client when the user has verified that
* both short codes do match
*/
fun userHasVerifiedShortCode()
suspend fun userHasVerifiedShortCode()
fun acceptVerification()
suspend fun acceptVerification()
fun shortCodeDoesNotMatch()
suspend fun shortCodeDoesNotMatch()
}

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.crypto.verification
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.LocalEcho
/**
@ -36,7 +35,7 @@ interface VerificationService {
/**
* Mark this device as verified manually
*/
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
@ -46,54 +45,37 @@ interface VerificationService {
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
fun beginKeyVerification(method: VerificationMethod,
otherUserId: String,
otherDeviceId: String,
transactionId: String?): String?
/**
* Request key verification with another user via room events (instead of the to-device API).
*/
suspend fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
/**
* Request key verification with another user via room events (instead of the to-device API)
* Request a self key verification using to-device API (instead of room events).
*/
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
fun cancelVerificationRequest(request: PendingVerificationRequest)
suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest
/**
* Request a key verification from another user using toDevice events.
* You should call this method after receiving a verification request.
* Accept the verification request advertising the given methods as supported
* Returns false if the request is unknown or transaction is not ready.
*/
fun requestKeyVerification(methods: List<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?): PendingVerificationRequest
suspend fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean
fun declineVerificationRequestInDMs(otherUserId: String,
transactionId: String,
roomId: String)
suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
// Only SAS method is supported for the moment
// TODO Parameter otherDeviceId should be removed in this case
fun beginKeyVerificationInDMs(method: VerificationMethod,
transactionId: String,
roomId: String,
otherUserId: String,
otherDeviceId: String): String
suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String)
/**
* Returns false if the request is unknown
*/
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
transactionId: String): Boolean
suspend fun beginKeyVerification(method: VerificationMethod,
otherUserId: String,
transactionId: String): String?
/**
* Returns false if the request is unknown
*/
fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean
suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String?
interface Listener {
/**
@ -137,6 +119,4 @@ interface VerificationService {
return age in tooInThePast..tooInTheFuture
}
}
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
}

View file

@ -30,9 +30,9 @@ interface VerificationTransaction {
/**
* User wants to cancel the transaction
*/
fun cancel()
suspend fun cancel()
fun cancel(code: CancelCode)
suspend fun cancel(code: CancelCode)
fun isToDeviceTransport(): Boolean
}

View file

@ -91,6 +91,7 @@ object EventType {
const val SEND_SECRET = "m.secret.send"
// Interactive key verification
const val KEY_VERIFICATION_REQUEST = "m.key.verification.request"
const val KEY_VERIFICATION_START = "m.key.verification.start"
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"
const val KEY_VERIFICATION_KEY = "m.key.verification.key"

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.room.model.message
import org.matrix.android.sdk.api.session.events.model.EventType
object MessageType {
const val MSGTYPE_TEXT = "m.text"
const val MSGTYPE_EMOTE = "m.emote"
@ -26,7 +28,7 @@ object MessageType {
const val MSGTYPE_LOCATION = "m.location"
const val MSGTYPE_FILE = "m.file"
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
const val MSGTYPE_VERIFICATION_REQUEST = EventType.KEY_VERIFICATION_REQUEST
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
// Because sticker isn't a message type but a event type without msgtype field

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.coroutines.builder
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.ProducerScope
/**
* Use this with a flow builder like [kotlinx.coroutines.flow.channelFlow] to replace [kotlinx.coroutines.channels.awaitClose].
* As awaitClose is at the end of the builder block, it can lead to the block being cancelled before it reaches the awaitClose.
* Example of usage:
*
* return channelFlow {
* val onClose = safeInvokeOnClose {
* // Do stuff on close
* }
* val data = getData()
* send(data)
* onClose.await()
* }
*
*/
internal fun <T> ProducerScope<T>.safeInvokeOnClose(handler: (cause: Throwable?) -> Unit): CompletableDeferred<Unit> {
val onClose = CompletableDeferred<Unit>()
invokeOnClose {
handler(it)
onClose.complete(Unit)
}
return onClose
}

View file

@ -22,18 +22,17 @@ import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
@ -55,7 +54,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
@ -65,8 +63,9 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
@ -76,14 +75,9 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.StreamEventsManager
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.TaskThread
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import timber.log.Timber
import uniffi.olm.Request
import uniffi.olm.RequestType
@ -130,9 +124,8 @@ internal class DefaultCryptoService @Inject constructor(
private val loadRoomMembersTask: LoadRoomMembersTask,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope,
private val sender: RequestSender,
private val requestSender: RequestSender,
private val crossSigningService: CrossSigningService,
private val verificationService: RustVerificationService,
private val keysBackupService: RustKeyBackupService,
@ -153,7 +146,12 @@ internal class DefaultCryptoService @Inject constructor(
// Locks for some of our operations
private val keyClaimLock: Mutex = Mutex()
private val outgoingRequestsLock: Mutex = Mutex()
private val outgoingRequestsProcessor = OutgoingRequestsProcessor(
requestSender = requestSender,
coroutineScope = cryptoCoroutineScope,
cryptoSessionInfoProvider = cryptoSessionInfoProvider,
shieldComputer = crossSigningService::shieldForGroup
)
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap()
fun onStateEvent(roomId: String, event: Event) {
@ -166,51 +164,33 @@ internal class DefaultCryptoService @Inject constructor(
fun onLiveEvent(roomId: String, event: Event) {
if (event.isStateEvent()) {
when (event.getClearType()) {
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
else -> cryptoCoroutineScope.launch {
this@DefaultCryptoService.verificationService.onEvent(event)
when (event.getClearType()) {
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
}
} else {
cryptoCoroutineScope.launch {
verificationService.onEvent(roomId, event)
}
}
}
}
private val gossipingBuffer = mutableListOf<Event>()
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// bg refresh of crypto device
cryptoCoroutineScope.launch {
try {
downloadKeys(listOf(userId), true)
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
}
}
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
override suspend fun setDeviceName(deviceId: String, deviceName: String) {
val params = SetDeviceNameTask.Params(deviceId, deviceName)
setDeviceNameTask.execute(params)
try {
downloadKeys(listOf(userId), true)
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
}
}
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
deleteDeviceTask
.configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
val params = DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)
deleteDeviceTask.execute(params)
}
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
@ -218,27 +198,16 @@ internal class DefaultCryptoService @Inject constructor(
return if (longFormat) "Rust SDK 0.3" else "0.3"
}
override fun getMyDevice(): CryptoDeviceInfo {
return runBlocking { olmMachine.ownDevice() }
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo {
return olmMachine.ownDevice()
}
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask
.configureWith {
// this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<DevicesListResponse> {
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
override fun onSuccess(data: DevicesListResponse) {
// Save in local DB
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
callback.onSuccess(data)
}
}
}
.executeBy(taskExecutor)
override suspend fun fetchDevicesList(): List<DeviceInfo> {
val devicesList = tryOrNull {
getDevicesTask.execute(Unit).devices
}.orEmpty()
cryptoStore.saveMyDevicesInfo(devicesList)
return devicesList
}
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
@ -249,16 +218,12 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getMyDevicesInfo()
}
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
getDeviceInfoTask
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
val params = GetDeviceInfoTask.Params(deviceId)
return getDeviceInfoTask.execute(params)
}
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
return if (onlyBackedUp) {
keysBackupService.getTotalNumbersOfBackedUpKeys()
} else {
@ -267,20 +232,6 @@ internal class DefaultCryptoService @Inject constructor(
// return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
}
/**
* Provides the tracking status
*
* @param userId the user id
* @return the tracking status
*/
override fun getDeviceTrackingStatus(userId: String): Int {
return if (olmMachine.isUserTracked(userId)) {
3
} else {
-1
}
}
/**
* Tell if the MXCrypto is started
*
@ -299,29 +250,13 @@ internal class DefaultCryptoService @Inject constructor(
*/
fun start() {
internalStart()
// Just update
fetchDevicesList(NoOpMatrixCallback())
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch {
// Just update
fetchDevicesList()
cryptoStore.tidyUpDataBase()
}
}
fun ensureDevice() {
cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
// Open the store
cryptoStore.open()
// this can throw if no backup
/*
TODO
tryOrNull {
keysBackupService.checkAndStartKeysBackup()
}
*/
}
}
private fun internalStart() {
if (isStarted.get() || isStarting.get()) {
return
@ -355,9 +290,13 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Close the crypto
*/
fun close() = runBlocking(coroutineDispatchers.crypto) {
fun close() {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
cryptoStore.close()
cryptoCoroutineScope.launch {
withContext(coroutineDispatchers.crypto + NonCancellable) {
cryptoStore.close()
}
}
}
// Always enabled on Matrix Android SDK2
@ -380,7 +319,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
suspend fun onSyncCompleted() {
if (isStarted()) {
sendOutgoingRequests()
outgoingRequestsProcessor.process(olmMachine)
// This isn't a copy paste error. Sending the outgoing requests may
// claim one-time keys and establish 1-to-1 Olm sessions with devices, while some
// outgoing requests are waiting for an Olm session to be established (e.g. forwarding
@ -389,7 +328,7 @@ internal class DefaultCryptoService @Inject constructor(
// The second call sends out those requests that are waiting for the
// keys claim request to be sent out.
// This could be omitted but then devices might be waiting for the next
sendOutgoingRequests()
outgoingRequestsProcessor.process(olmMachine)
keysBackupService.maybeBackupKeys()
}
@ -410,41 +349,19 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the user id
* @param deviceId the device id
*/
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
runBlocking {
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId, deviceId)
}
} else {
null
}
override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
if (userId.isEmpty() || deviceId.isNullOrEmpty()) return null
return olmMachine.getCryptoDeviceInfo(userId, deviceId)
}
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return runBlocking {
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId)
}
override suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo> {
return olmMachine.getCryptoDeviceInfo(userId)
}
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
return getLiveCryptoDeviceInfo(listOf(userId))
}
override fun getLiveCryptoDeviceInfoList(userId: String) = getLiveCryptoDeviceInfoList(listOf(userId))
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
return runBlocking {
this@DefaultCryptoService.olmMachine.getLiveDevices(userIds) // ?: LiveDevice(userIds, deviceObserver)
}
}
/**
* Update the blocked/verified state of the given device.
*
* @param trustLevel the new trust level
* @param userId the owner of the device
* @param deviceId the unique identifier for the device.
*/
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
// TODO
override fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
return olmMachine.getLiveDevices(userIds)
}
/**
@ -503,8 +420,8 @@ internal class DefaultCryptoService @Inject constructor(
/**
* @return the stored device keys for a user.
*/
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
return this.getCryptoDeviceInfo(userId).toMutableList()
override suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
return this.getCryptoDeviceInfoList(userId).toMutableList()
}
private fun isEncryptionEnabledForInvitedUser(): Boolean {
@ -533,34 +450,29 @@ internal class DefaultCryptoService @Inject constructor(
* @param eventContent the content of the event.
* @param eventType the type of the event.
* @param roomId the room identifier the event will be sent.
* @param callback the asynchronous callback
*/
override fun encryptEventContent(eventContent: Content,
eventType: String,
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) {
override suspend fun encryptEventContent(eventContent: Content,
eventType: String,
roomId: String): MXEncryptEventContentResult {
// moved to crypto scope to have up to date values
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
return withContext(coroutineDispatchers.crypto) {
val algorithm = getEncryptionAlgorithm(roomId)
if (algorithm != null) {
val userIds = getRoomUserIds(roomId)
val t0 = System.currentTimeMillis()
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
runCatching {
measureTimeMillis {
preshareRoomKey(roomId, userIds)
}.also {
Timber.d("Shared room key in room $roomId took $it ms")
}
val content = encrypt(roomId, eventType, eventContent)
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
measureTimeMillis {
preshareRoomKey(roomId, userIds)
}.also {
Timber.d("Shared room key in room $roomId took $it ms")
}
val content = encrypt(roomId, eventType, eventContent)
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
} else {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))
}
}
}
@ -577,22 +489,8 @@ internal class DefaultCryptoService @Inject constructor(
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking {
olmMachine.decryptRoomEvent(event)
}
}
/**
* Decrypt an event asynchronously
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
// This isn't really used anywhere, maybe just remove it?
// TODO
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return olmMachine.decryptRoomEvent(event)
}
/**
@ -616,7 +514,6 @@ internal class DefaultCryptoService @Inject constructor(
// Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
// } finally {
val userIds = getRoomUserIds(roomId)
olmMachine.updateTrackedUsers(userIds)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds)
// }
}
@ -721,7 +618,7 @@ internal class DefaultCryptoService @Inject constructor(
this.keysBackupService.onSecretKeyGossip(secretContent.secretValue)
}
else -> {
this.verificationService.onEvent(event)
this.verificationService.onEvent(null, event)
}
}
liveEventManager.get().dispatchOnLiveToDevice(event)
@ -730,26 +627,12 @@ internal class DefaultCryptoService @Inject constructor(
}
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>) {
keyClaimLock.withLock {
val request = this.olmMachine.getMissingSessions(roomMembers)
// This request can only be a keys claim request.
if (request != null) {
when (request) {
is Request.KeysClaim -> {
claimKeys(request)
}
else -> {
}
}
}
}
val keyShareLock = roomKeyShareLocks.getOrPut(roomId, { Mutex() })
claimMissingKeys(roomMembers)
val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() }
var sharedKey = false
keyShareLock.withLock {
coroutineScope {
this@DefaultCryptoService.olmMachine.shareRoomKey(roomId, roomMembers).map {
olmMachine.shareRoomKey(roomId, roomMembers).map {
when (it) {
is Request.ToDevice -> {
sharedKey = true
@ -775,19 +658,35 @@ internal class DefaultCryptoService @Inject constructor(
}
}
private suspend fun claimMissingKeys(roomMembers: List<String>) = keyClaimLock.withLock {
val request = this.olmMachine.getMissingSessions(roomMembers)
// This request can only be a keys claim request.
when (request) {
is Request.KeysClaim -> {
claimKeys(request)
}
else -> {
}
}
}
private suspend fun encrypt(roomId: String, eventType: String, content: Content): Content {
return olmMachine.encrypt(roomId, eventType, content)
}
private suspend fun uploadKeys(request: Request.KeysUpload) {
val response = this.sender.uploadKeys(request)
this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
try {
val response = requestSender.uploadKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO uploadKeys(): error")
}
}
private suspend fun queryKeys(request: Request.KeysQuery) {
try {
val response = this.sender.queryKeys(request)
this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
val response = requestSender.queryKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
// Update the shields!
cryptoCoroutineScope.launch {
@ -798,69 +697,44 @@ internal class DefaultCryptoService @Inject constructor(
}
}
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO doKeyDownloadForUsers(): error")
}
}
private suspend fun sendToDevice(request: Request.ToDevice) {
this.sender.sendToDevice(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
try {
requestSender.sendToDevice(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendToDevice(): error")
}
}
private suspend fun claimKeys(request: Request.KeysClaim) {
val response = this.sender.claimKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
try {
val response = requestSender.claimKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO claimKeys(): error")
}
}
private suspend fun signatureUpload(request: Request.SignatureUpload) {
this.sender.sendSignatureUpload(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}")
try {
val response = requestSender.sendSignatureUpload(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO signatureUpload(): error")
}
}
private suspend fun sendOutgoingRequests() {
outgoingRequestsLock.withLock {
coroutineScope {
olmMachine.outgoingRequests().map {
when (it) {
is Request.KeysUpload -> {
async {
uploadKeys(it)
}
}
is Request.KeysQuery -> {
async {
queryKeys(it)
}
}
is Request.ToDevice -> {
async {
sendToDevice(it)
}
}
is Request.KeysClaim -> {
async {
claimKeys(it)
}
}
is Request.RoomMessage -> {
async {
sender.sendRoomMessage(it)
}
}
is Request.SignatureUpload -> {
async {
signatureUpload(it)
}
}
is Request.KeysBackup -> {
async {
// The rust-sdk won't ever produce KeysBackup requests here,
// those only get explicitly created.
}
}
}
}.joinAll()
}
private suspend fun sendRoomMessage(request: Request.RoomMessage) {
try {
Timber.v("SendRoomMessage: $request")
val response = requestSender.sendRoomMessage(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.ROOM_MESSAGE, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendRoomMessage(): error")
}
}
@ -991,18 +865,17 @@ internal class DefaultCryptoService @Inject constructor(
val cancellation = requestPair.cancellation
val request = requestPair.keyRequest
if (cancellation != null) {
when (cancellation) {
is Request.ToDevice -> {
sendToDevice(cancellation)
}
when (cancellation) {
is Request.ToDevice -> {
sendToDevice(cancellation)
}
else -> Unit
}
when (request) {
is Request.ToDevice -> {
sendToDevice(request)
}
else -> Unit
}
}
}
@ -1082,18 +955,16 @@ internal class DefaultCryptoService @Inject constructor(
cryptoStore.logDbUsageInfo()
}
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
override suspend fun prepareToEncrypt(roomId: String) {
withContext(coroutineDispatchers.crypto) {
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
// Ensure to load all room members
try {
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
callback.onFailure(failure)
return@launch
throw failure
}
val userIds = getRoomUserIds(roomId)
val algorithm = getEncryptionAlgorithm(roomId)
@ -1101,19 +972,13 @@ internal class DefaultCryptoService @Inject constructor(
if (algorithm == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm"))
return@launch
throw IllegalArgumentException("Missing algorithm")
}
runCatching {
try {
preshareRoomKey(roomId, userIds)
}.fold(
{ callback.onSuccess(Unit) },
{
Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
callback.onFailure(it)
}
)
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to PreshareRoomKey")
}
}
}

View file

@ -16,13 +16,14 @@
package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreException
import uniffi.olm.OlmMachine
@ -39,16 +40,17 @@ internal class Device(
private val machine: OlmMachine,
private var inner: InnerDevice,
private val sender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val listeners: ArrayList<VerificationService.Listener>
) {
@Throws(CryptoStoreException::class)
private suspend fun refreshData() {
val device = withContext(Dispatchers.IO) {
val device = withContext(coroutineDispatchers.io) {
machine.getDevice(inner.userId, inner.deviceId)
}
if (device != null) {
this.inner = device
inner = device
}
}
@ -66,12 +68,12 @@ internal class Device(
@Throws(CryptoStoreException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
val stringMethods = prepareMethods(methods)
val result = withContext(Dispatchers.IO) {
val result = withContext(coroutineDispatchers.io) {
machine.requestVerificationWithDevice(inner.userId, inner.deviceId, stringMethods)
}
return if (result != null) {
this.sender.sendVerificationRequest(result.request)
sender.sendVerificationRequest(result.request)
result.verification
} else {
null
@ -89,14 +91,18 @@ internal class Device(
*/
@Throws(CryptoStoreException::class)
suspend fun startVerification(): SasVerification? {
val result = withContext(Dispatchers.IO) {
val result = withContext(coroutineDispatchers.io) {
machine.startSasWithDevice(inner.userId, inner.deviceId)
}
return if (result != null) {
this.sender.sendVerificationRequest(result.request)
sender.sendVerificationRequest(result.request)
SasVerification(
this.machine, result.sas, this.sender, this.listeners,
machine = machine,
inner = result.sas,
sender = sender,
coroutineDispatchers = coroutineDispatchers,
listeners = listeners
)
} else {
null
@ -111,7 +117,7 @@ internal class Device(
*/
@Throws(CryptoStoreException::class)
suspend fun markAsTrusted() {
withContext(Dispatchers.IO) {
withContext(coroutineDispatchers.io) {
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
}
}
@ -127,11 +133,11 @@ internal class Device(
*/
@Throws(SignatureException::class)
suspend fun verify(): Boolean {
val request = withContext(Dispatchers.IO) {
val request = withContext(coroutineDispatchers.io) {
machine.verifyDevice(inner.userId, inner.deviceId)
}
this.sender.sendSignatureUpload(request)
sender.sendSignatureUpload(request)
return true
}
@ -151,20 +157,20 @@ internal class Device(
* This will not fetch out fresh data from the Rust side.
**/
internal fun toCryptoDeviceInfo(): CryptoDeviceInfo {
val keys = this.inner.keys.map { (keyId, key) -> "$keyId:$this.inner.deviceId" to key }.toMap()
val keys = inner.keys.map { (keyId, key) -> "$keyId:$inner.deviceId" to key }.toMap()
return CryptoDeviceInfo(
this.inner.deviceId,
this.inner.userId,
this.inner.algorithms,
keys,
deviceId = inner.deviceId,
userId = inner.userId,
algorithms = inner.algorithms,
keys = keys,
// The Kotlin side doesn't need to care about signatures,
// so we're not filling this out
mapOf(),
UnsignedDeviceInfo(this.inner.displayName),
DeviceTrustLevel(crossSigningVerified = this.inner.crossSigningTrusted, locallyVerified = this.inner.locallyTrusted),
this.inner.isBlocked,
signatures = mapOf(),
unsigned = UnsignedDeviceInfo(inner.displayName),
trustLevel = DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted),
isBlocked = inner.isBlocked,
// TODO
null)
firstTimeSeenLocalTs = null)
}
}

View file

@ -16,11 +16,13 @@
package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.Dispatchers
import com.squareup.moshi.Moshi
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.listeners.ProgressListener
@ -36,6 +38,7 @@ import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
@ -44,8 +47,8 @@ import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
import timber.log.Timber
import uniffi.olm.BackupKeys
@ -64,7 +67,6 @@ import uniffi.olm.setLogger
import java.io.File
import java.nio.charset.Charset
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import uniffi.olm.OlmMachine as InnerMachine
import uniffi.olm.ProgressListener as RustProgressListener
import uniffi.olm.UserIdentity as RustUserIdentity
@ -81,138 +83,71 @@ private class CryptoProgressListener(private val listener: ProgressListener?) :
}
}
internal class LiveDevice(
internal var userIds: List<String>,
private var observer: DeviceUpdateObserver
) : MutableLiveData<List<CryptoDeviceInfo>>() {
private data class UserIdentityCollector(val userId: String, val collector: SendChannel<Optional<MXCrossSigningInfo>>)
: SendChannel<Optional<MXCrossSigningInfo>> by collector
private data class DevicesCollector(val userIds: List<String>, val collector: SendChannel<List<CryptoDeviceInfo>>)
: SendChannel<List<CryptoDeviceInfo>> by collector
private typealias PrivateKeysCollector = SendChannel<Optional<PrivateKeysInfo>>
override fun onActive() {
observer.addDeviceUpdateListener(this)
}
override fun onInactive() {
observer.removeDeviceUpdateListener(this)
}
}
internal class LiveUserIdentity(
internal var userId: String,
private var observer: UserIdentityUpdateObserver
) : MutableLiveData<Optional<MXCrossSigningInfo>>() {
override fun onActive() {
observer.addUserIdentityUpdateListener(this)
}
override fun onInactive() {
observer.removeUserIdentityUpdateListener(this)
}
}
internal class LivePrivateCrossSigningKeys(
private var observer: PrivateCrossSigningKeysUpdateObserver,
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
override fun onActive() {
observer.addUserIdentityUpdateListener(this)
}
override fun onInactive() {
observer.removeUserIdentityUpdateListener(this)
}
private class FlowCollectors {
val userIdentityCollectors = ArrayList<UserIdentityCollector>()
val privateKeyCollectors = ArrayList<PrivateKeysCollector>()
val deviceCollectors = ArrayList<DevicesCollector>()
}
fun setRustLogger() {
setLogger(CryptoLogger() as Logger)
}
internal class DeviceUpdateObserver {
internal val listeners = ConcurrentHashMap<LiveDevice, List<String>>()
fun addDeviceUpdateListener(device: LiveDevice) {
listeners[device] = device.userIds
}
fun removeDeviceUpdateListener(device: LiveDevice) {
listeners.remove(device)
}
}
internal class UserIdentityUpdateObserver {
internal val listeners = ConcurrentHashMap<LiveUserIdentity, String>()
fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
listeners[userIdentity] = userIdentity.userId
}
fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
listeners.remove(userIdentity)
}
}
internal class PrivateCrossSigningKeysUpdateObserver {
internal val listeners = ConcurrentHashMap<LivePrivateCrossSigningKeys, Unit>()
fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
listeners[liveKeys] = Unit
}
fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
listeners.remove(liveKeys)
}
}
internal class OlmMachine(
user_id: String,
device_id: String,
path: File,
deviceObserver: DeviceUpdateObserver,
private val requestSender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val moshi: Moshi
) {
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
private val deviceUpdateObserver = deviceObserver
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString(), null)
internal val verificationListeners = ArrayList<VerificationService.Listener>()
private val flowCollectors = FlowCollectors()
/** Get our own user ID. */
fun userId(): String {
return this.inner.userId()
return inner.userId()
}
/** Get our own device ID. */
fun deviceId(): String {
return this.inner.deviceId()
return inner.deviceId()
}
/** Get our own public identity keys ID. */
fun identityKeys(): Map<String, String> {
return this.inner.identityKeys()
return inner.identityKeys()
}
fun inner(): InnerMachine {
return this.inner
return inner
}
/** Update all of our live device listeners. */
private suspend fun updateLiveDevices() {
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
val devices = getCryptoDeviceInfo(users)
liveDevice.postValue(devices)
for (deviceCollector in flowCollectors.deviceCollectors) {
val devices = getCryptoDeviceInfo(deviceCollector.userIds)
deviceCollector.trySend(devices)
}
}
private suspend fun updateLiveUserIdentities() {
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) {
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
liveIdentity.postValue(identity)
for (userIdentityCollector in flowCollectors.userIdentityCollectors) {
val identity = getIdentity(userIdentityCollector.userId)?.toMxCrossSigningInfo()
userIdentityCollector.trySend(identity.toOptional())
}
}
private suspend fun updateLivePrivateKeys() {
val keys = this.exportCrossSigningKeys().toOptional()
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) {
liveKeys.postValue(keys)
val keys = exportCrossSigningKeys().toOptional()
for (privateKeyCollector in flowCollectors.privateKeyCollectors) {
privateKeyCollector.trySend(keys)
}
}
@ -220,18 +155,18 @@ internal class OlmMachine(
* Get our own device info as [CryptoDeviceInfo].
*/
suspend fun ownDevice(): CryptoDeviceInfo {
val deviceId = this.deviceId()
val deviceId = deviceId()
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
val keys = identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
val crossSigningVerified = when (val ownIdentity = this.getIdentity(this.userId())) {
val crossSigningVerified = when (val ownIdentity = getIdentity(userId())) {
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
else -> false
}
return CryptoDeviceInfo(
this.deviceId(),
this.userId(),
deviceId(),
userId(),
// TODO pass the algorithms here.
listOf(),
keys,
@ -251,7 +186,7 @@ internal class OlmMachine(
* @return the list of requests that needs to be sent to the homeserver
*/
suspend fun outgoingRequests(): List<Request> =
withContext(Dispatchers.IO) { inner.outgoingRequests() }
withContext(coroutineDispatchers.io) { inner.outgoingRequests() }
/**
* Mark a request that was sent to the server as sent.
@ -268,9 +203,8 @@ internal class OlmMachine(
requestType: RequestType,
responseBody: String
) =
withContext(Dispatchers.IO) {
withContext(coroutineDispatchers.io) {
inner.markRequestAsSent(requestId, requestType, responseBody)
if (requestType == RequestType.KEYS_QUERY) {
updateLiveDevices()
updateLiveUserIdentities()
@ -296,7 +230,7 @@ internal class OlmMachine(
deviceChanges: DeviceListResponse?,
keyCounts: DeviceOneTimeKeysCountSyncResponse?
): ToDeviceSyncResponse {
val response = withContext(Dispatchers.IO) {
val response = withContext(coroutineDispatchers.io) {
val counts: MutableMap<String, Int> = mutableMapOf()
if (keyCounts?.signedCurve25519 != null) {
@ -306,7 +240,7 @@ internal class OlmMachine(
val devices =
DeviceLists(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
val adapter =
MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
moshi.adapter(ToDeviceSyncResponse::class.java)
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
// TODO once our sync response type parses the unused fallback key
@ -315,11 +249,17 @@ internal class OlmMachine(
}
// We may get cross signing keys over a to-device event, update our listeners.
this.updateLivePrivateKeys()
updateLivePrivateKeys()
return response
}
suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) {
val adapter = moshi.adapter(Event::class.java)
val serializedEvent = adapter.toJson(event)
inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId)
}
/**
* Mark the given list of users to be tracked, triggering a key query request for them.
*
@ -329,7 +269,7 @@ internal class OlmMachine(
* @param users The users that should be queued up for a key query.
*/
suspend fun updateTrackedUsers(users: List<String>) =
withContext(Dispatchers.IO) { inner.updateTrackedUsers(users) }
withContext(coroutineDispatchers.io) { inner.updateTrackedUsers(users) }
/**
* Check if the given user is considered to be tracked.
@ -338,7 +278,7 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
fun isUserTracked(userId: String): Boolean {
return this.inner.isUserTracked(userId)
return inner.isUserTracked(userId)
}
/**
@ -355,7 +295,7 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
suspend fun getMissingSessions(users: List<String>): Request? =
withContext(Dispatchers.IO) { inner.getMissingSessions(users) }
withContext(coroutineDispatchers.io) { inner.getMissingSessions(users) }
/**
* Share a room key with the given list of users for the given room.
@ -377,7 +317,7 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) }
withContext(coroutineDispatchers.io) { inner.shareRoomKey(roomId, users) }
/**
* Encrypt the given event with the given type and content for the given room.
@ -411,8 +351,8 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
withContext(coroutineDispatchers.io) {
val adapter = moshi.adapter<Content>(Map::class.java)
val contentString = adapter.toJson(content)
val encrypted = inner.encrypt(roomId, eventType, contentString)
adapter.fromJson(encrypted)!!
@ -429,17 +369,17 @@ internal class OlmMachine(
*/
@Throws(MXCryptoError::class)
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
val serializedEvent = adapter.toJson(event)
withContext(coroutineDispatchers.io) {
val adapter = moshi.adapter(Event::class.java)
try {
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
val serializedEvent = adapter.toJson(event)
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId)
val deserializationAdapter =
MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java)
moshi.adapter<JsonDict>(Map::class.java)
val clearEvent = deserializationAdapter.fromJson(decrypted.clearEvent)
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
@ -469,8 +409,8 @@ internal class OlmMachine(
*/
@Throws(DecryptionException::class)
suspend fun requestRoomKey(event: Event): KeyRequestPair =
withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
withContext(coroutineDispatchers.io) {
val adapter = moshi.adapter(Event::class.java)
val serializedEvent = adapter.toJson(event)
inner.requestRoomKey(serializedEvent, event.roomId!!)
@ -488,7 +428,7 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray =
withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() }
withContext(coroutineDispatchers.io) { inner.exportKeys(passphrase, rounds).toByteArray() }
/**
* Import room keys from the given serialized key export.
@ -505,7 +445,7 @@ internal class OlmMachine(
passphrase: String,
listener: ProgressListener?
): ImportRoomKeysResult =
withContext(Dispatchers.IO) {
withContext(coroutineDispatchers.io) {
val decodedKeys = String(keys, Charset.defaultCharset())
val rustListener = CryptoProgressListener(listener)
@ -520,8 +460,8 @@ internal class OlmMachine(
keys: List<MegolmSessionData>,
listener: ProgressListener?
): ImportRoomKeysResult =
withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter(List::class.java)
withContext(coroutineDispatchers.io) {
val adapter = moshi.adapter(List::class.java)
// If the key backup is too big we take the risk of causing OOM
// when serializing to json
@ -550,25 +490,31 @@ internal class OlmMachine(
@Throws(CryptoStoreException::class)
suspend fun getIdentity(userId: String): UserIdentities? {
val identity = withContext(Dispatchers.IO) {
val identity = withContext(coroutineDispatchers.io) {
inner.getIdentity(userId)
}
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
val adapter = moshi.adapter(RestKeyInfo::class.java)
return when (identity) {
is RustUserIdentity.Other -> {
val verified = this.inner().isIdentityVerified(userId)
val verified = inner().isIdentityVerified(userId)
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
trustLevel = DeviceTrustLevel(verified, verified)
}
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel().apply {
trustLevel = DeviceTrustLevel(verified, verified)
}
UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender)
UserIdentity(
userId = identity.userId,
masterKey = masterKey,
selfSigningKey = selfSigningKey,
olmMachine = this,
requestSender = requestSender,
coroutineDispatchers = coroutineDispatchers
)
}
is RustUserIdentity.Own -> {
val verified = this.inner().isIdentityVerified(userId)
val verified = inner().isIdentityVerified(userId)
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
trustLevel = DeviceTrustLevel(verified, verified)
@ -579,13 +525,14 @@ internal class OlmMachine(
val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel()
OwnUserIdentity(
identity.userId,
masterKey,
selfSigningKey,
userSigningKey,
identity.trustsOurOwnDevice,
this,
this.requestSender
userId = identity.userId,
masterKey = masterKey,
selfSigningKey = selfSigningKey,
userSigningKey = userSigningKey,
trustsOurOwnDevice = identity.trustsOurOwnDevice,
olmMachine = this,
requestSender = requestSender,
coroutineDispatchers = coroutineDispatchers
)
}
null -> null
@ -605,27 +552,35 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? {
return if (userId == userId() && deviceId == deviceId()) {
// Our own device isn't part of our store on the Rust side, return it
// using our ownDevice method
ownDevice()
} else {
getDevice(userId, deviceId)?.toCryptoDeviceInfo()
}
return getDevice(userId, deviceId)?.toCryptoDeviceInfo()
}
@Throws(CryptoStoreException::class)
suspend fun getDevice(userId: String, deviceId: String): Device? {
val device = withContext(Dispatchers.IO) {
val device = withContext(coroutineDispatchers.io) {
inner.getDevice(userId, deviceId)
} ?: return null
return Device(this.inner, device, this.requestSender, this.verificationListeners)
return Device(
machine = inner,
inner = device,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners
)
}
suspend fun getUserDevices(userId: String): List<Device> {
return withContext(Dispatchers.IO) {
inner.getUserDevices(userId).map { Device(inner, it, requestSender, verificationListeners) }
return withContext(coroutineDispatchers.io) {
inner.getUserDevices(userId).map {
Device(
machine = inner,
inner = it,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners
)
}
}
}
@ -638,15 +593,17 @@ internal class OlmMachine(
*/
@Throws(CryptoStoreException::class)
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
return getUserDevices(userId).map { it.toCryptoDeviceInfo() }
/*
// EA doesn't differentiate much between our own and other devices of
// while the rust-sdk does, append our own device here.
if (userId == this.userId()) {
devices.add(this.ownDevice())
if (userId == userId()) {
devices.add(ownDevice())
}
return devices
*/
}
/**
@ -660,7 +617,7 @@ internal class OlmMachine(
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
for (user in userIds) {
val devices = this.getCryptoDeviceInfo(user)
val devices = getCryptoDeviceInfo(user)
plainDevices.addAll(devices)
}
@ -669,7 +626,7 @@ internal class OlmMachine(
@Throws
suspend fun forceKeyDownload(userIds: List<String>) {
withContext(Dispatchers.IO) {
withContext(coroutineDispatchers.io) {
val requestId = UUID.randomUUID().toString()
val response = requestSender.queryKeys(Request.KeysQuery(requestId, userIds))
markRequestAsSent(requestId, RequestType.KEYS_QUERY, response)
@ -680,7 +637,7 @@ internal class OlmMachine(
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
for (user in userIds) {
val devices = this.getCryptoDeviceInfo(user)
val devices = getCryptoDeviceInfo(user)
for (device in devices) {
userMap.setObject(user, device.deviceId, device)
@ -697,7 +654,18 @@ internal class OlmMachine(
* The key query request will be retried a few time in case of shaky connection, but could fail.
*/
suspend fun ensureUserDevicesMap(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo> {
val toDownload = if (forceDownload) {
ensureUsersKeys(userIds, forceDownload)
return getUserDevicesMap(userIds)
}
/**
* If the user is untracked or forceDownload is set to true, a key query request will be made.
* It will suspend until query response.
*
* The key query request will be retried a few time in case of shaky connection, but could fail.
*/
suspend fun ensureUsersKeys(userIds: List<String>, forceDownload: Boolean = false) {
val userIdsToFetchKeys = if (forceDownload) {
userIds
} else {
userIds.mapNotNull { userId ->
@ -706,26 +674,34 @@ internal class OlmMachine(
updateTrackedUsers(it)
}
}
tryOrNull("Failed to download keys for $toDownload") {
forceKeyDownload(toDownload)
tryOrNull("Failed to download keys for $userIdsToFetchKeys") {
forceKeyDownload(userIdsToFetchKeys)
}
return getUserDevicesMap(userIds)
}
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver)
liveIdentity.value = identity
return liveIdentity
fun getLiveUserIdentity(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return channelFlow {
val userIdentityCollector = UserIdentityCollector(userId, this)
val onClose = safeInvokeOnClose {
flowCollectors.userIdentityCollectors.remove(userIdentityCollector)
}
flowCollectors.userIdentityCollectors.add(userIdentityCollector)
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
send(identity)
onClose.await()
}
}
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> {
val keys = this.exportCrossSigningKeys().toOptional()
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver)
liveKeys.value = keys
return liveKeys
fun getLivePrivateCrossSigningKeys(): Flow<Optional<PrivateKeysInfo>> {
return channelFlow {
val onClose = safeInvokeOnClose {
flowCollectors.privateKeyCollectors.remove(this)
}
flowCollectors.privateKeyCollectors.add(this)
val keys = this@OlmMachine.exportCrossSigningKeys().toOptional()
send(keys)
onClose.await()
}
}
/**
@ -736,14 +712,19 @@ internal class OlmMachine(
*
* @param userIds The ids of the device owners.
*
* @return The list of Devices or an empty list if there aren't any.
* @return The list of Devices or an empty list if there aren't any as a Flow.
*/
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
val plainDevices = getCryptoDeviceInfo(userIds)
val devices = LiveDevice(userIds, deviceUpdateObserver)
devices.value = plainDevices
return devices
fun getLiveDevices(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
return channelFlow {
val devicesCollector = DevicesCollector(userIds, this)
val onClose = safeInvokeOnClose {
flowCollectors.deviceCollectors.remove(devicesCollector)
}
flowCollectors.deviceCollectors.add(devicesCollector)
val devices = getCryptoDeviceInfo(userIds)
send(devices)
onClose.await()
}
}
/** Discard the currently active room key for the given room if there is one. */
@ -760,26 +741,27 @@ internal class OlmMachine(
* @return The list of [VerificationRequest] that we share with the given user
*/
fun getVerificationRequests(userId: String): List<VerificationRequest> {
return this.inner.getVerificationRequests(userId).map {
return inner.getVerificationRequests(userId).map {
VerificationRequest(
this.inner,
it,
this.requestSender,
this.verificationListeners,
machine = inner,
inner = it,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners,
)
}
}
/** Get a verification request for the given user with the given flow ID */
fun getVerificationRequest(userId: String, flowId: String): VerificationRequest? {
val request = this.inner.getVerificationRequest(userId, flowId)
val request = inner.getVerificationRequest(userId, flowId)
return if (request != null) {
VerificationRequest(
this.inner,
request,
requestSender,
this.verificationListeners,
machine = inner,
inner = request,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners,
)
} else {
null
@ -792,13 +774,26 @@ internal class OlmMachine(
* verification.
*/
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
return when (val verification = this.inner.getVerification(userId, flowId)) {
return when (val verification = inner.getVerification(userId, flowId)) {
is uniffi.olm.Verification.QrCodeV1 -> {
val request = this.getVerificationRequest(userId, flowId) ?: return null
QrCodeVerification(inner, request, verification.qrcode, requestSender, verificationListeners)
val request = getVerificationRequest(userId, flowId) ?: return null
QrCodeVerification(
machine = inner,
request = request,
inner = verification.qrcode,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners
)
}
is uniffi.olm.Verification.SasV1 -> {
SasVerification(inner, verification.sas, requestSender, verificationListeners)
SasVerification(
machine = inner,
inner = verification.sas,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners
)
}
null -> {
// This branch exists because scanning a QR code is tied to the QrCodeVerification,
@ -808,7 +803,14 @@ internal class OlmMachine(
val request = getVerificationRequest(userId, flowId) ?: return null
if (request.canScanQrCodes()) {
QrCodeVerification(inner, request, null, requestSender, verificationListeners)
QrCodeVerification(
machine = inner,
request = request,
inner = null,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = verificationListeners
)
} else {
null
}
@ -817,23 +819,22 @@ internal class OlmMachine(
}
suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
val requests = withContext(Dispatchers.IO) {
val requests = withContext(coroutineDispatchers.io) {
inner.bootstrapCrossSigning()
}
this.requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
this.requestSender.sendSignatureUpload(requests.signatureRequest)
requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
requestSender.sendSignatureUpload(requests.signatureRequest)
}
/**
* Get the status of our private cross signing keys, i.e. which private keys do we have stored locally.
*/
fun crossSigningStatus(): CrossSigningStatus {
return this.inner.crossSigningStatus()
return inner.crossSigningStatus()
}
suspend fun exportCrossSigningKeys(): PrivateKeysInfo? {
val export = withContext(Dispatchers.IO) {
val export = withContext(coroutineDispatchers.io) {
inner.exportCrossSigningKeys()
} ?: return null
@ -844,7 +845,7 @@ internal class OlmMachine(
val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user)
var result: UserTrustResult
withContext(Dispatchers.IO) {
withContext(coroutineDispatchers.io) {
result = try {
inner.importCrossSigningKeys(rustExport)
@ -861,21 +862,21 @@ internal class OlmMachine(
UserTrustResult.Failure(failure.localizedMessage)
}
}
withContext(Dispatchers.Main) {
withContext(coroutineDispatchers.main) {
this@OlmMachine.updateLivePrivateKeys()
}
return result
}
suspend fun sign(message: String): Map<String, Map<String, String>> {
return withContext(Dispatchers.Default) {
return withContext(coroutineDispatchers.computation) {
inner.sign(message)
}
}
@Throws(CryptoStoreException::class)
suspend fun enableBackupV1(key: String, version: String) {
return withContext(Dispatchers.Default) {
return withContext(coroutineDispatchers.computation) {
val backupKey = MegolmV1BackupKey(key, mapOf(), null, MXCRYPTO_ALGORITHM_MEGOLM_BACKUP)
inner.enableBackupV1(backupKey, version)
}
@ -891,26 +892,29 @@ internal class OlmMachine(
}
@Throws(CryptoStoreException::class)
fun roomKeyCounts(): RoomKeyCounts {
// TODO convert this to a suspendable method
return inner.roomKeyCounts()
suspend fun roomKeyCounts(): RoomKeyCounts {
return withContext(coroutineDispatchers.computation) {
inner.roomKeyCounts()
}
}
@Throws(CryptoStoreException::class)
fun getBackupKeys(): BackupKeys? {
// TODO this needs to be suspendable
return inner.getBackupKeys()
suspend fun getBackupKeys(): BackupKeys? {
return withContext(coroutineDispatchers.computation) {
inner.getBackupKeys()
}
}
@Throws(CryptoStoreException::class)
fun saveRecoveryKey(key: String?, version: String?) {
// TODO convert this to a suspendable method
inner.saveRecoveryKey(key, version)
suspend fun saveRecoveryKey(key: String?, version: String?) {
withContext(coroutineDispatchers.computation) {
inner.saveRecoveryKey(key, version)
}
}
@Throws(CryptoStoreException::class)
suspend fun backupRoomKeys(): Request? {
return withContext(Dispatchers.Default) {
return withContext(coroutineDispatchers.computation) {
Timber.d("BACKUP CREATING REQUEST")
val request = inner.backupRoomKeys()
Timber.d("BACKUP CREATED REQUEST: $request")
@ -920,9 +924,8 @@ internal class OlmMachine(
@Throws(CryptoStoreException::class)
suspend fun checkAuthDataSignature(authData: MegolmBackupAuthData): Boolean {
return withContext(Dispatchers.Default) {
val adapter = MoshiProvider
.providesMoshi()
return withContext(coroutineDispatchers.computation) {
val adapter = moshi
.newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build()

View file

@ -16,6 +16,9 @@
package org.matrix.android.sdk.internal.crypto
import com.squareup.moshi.Moshi
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserId
@ -28,10 +31,16 @@ internal class OlmMachineProvider @Inject constructor(
@UserId private val userId: String,
@DeviceId private val deviceId: String?,
@SessionFilesDirectory private val dataDir: File,
requestSender: RequestSender
requestSender: RequestSender,
coroutineDispatchers: MatrixCoroutineDispatchers,
moshi: Moshi
) {
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, requestSender)
var olmMachine: OlmMachine = OlmMachine(
user_id = userId,
device_id = deviceId!!,
path = dataDir,
requestSender = requestSender,
coroutineDispatchers = coroutineDispatchers,
moshi = moshi)
}

View file

@ -16,15 +16,15 @@
package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
import uniffi.olm.CryptoStoreException
import uniffi.olm.OlmMachine
@ -37,13 +37,15 @@ internal class QrCodeVerification(
private var request: VerificationRequest,
private var inner: QrCode?,
private val sender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
listeners: ArrayList<VerificationService.Listener>
) : QrCodeVerificationTransaction {
private val dispatcher = UpdateDispatcher(listeners)
private fun dispatchTxUpdated() {
refreshData()
this.dispatcher.dispatchTxUpdated(this)
dispatcher.dispatchTxUpdated(this)
}
/** Generate, if possible, data that should be encoded as a QR code for QR code verification.
@ -60,27 +62,25 @@ internal class QrCodeVerification(
*/
override val qrCodeText: String?
get() {
val data = this.inner?.let { this.machine.generateQrCode(it.otherUserId, it.flowId) }
val data = inner?.let { machine.generateQrCode(it.otherUserId, it.flowId) }
// TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64?
return data?.fromBase64()?.toString(Charsets.ISO_8859_1)
}
/** Pass the data from a scanned QR code into the QR code verification object */
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
runBlocking {
request.scanQrCode(otherQrCodeText)
}
override suspend fun userHasScannedOtherQrCode(otherQrCodeText: String) {
request.scanQrCode(otherQrCodeText)
dispatchTxUpdated()
}
/** Confirm that the other side has indeed scanned the QR code we presented */
override fun otherUserScannedMyQrCode() {
runBlocking { confirm() }
override suspend fun otherUserScannedMyQrCode() {
confirm()
}
/** Cancel the QR code verification, denying that the other side has scanned the QR code */
override fun otherUserDidNotScannedMyQrCode() {
override suspend fun otherUserDidNotScannedMyQrCode() {
// TODO Is this code correct here? The old code seems to do this
cancelHelper(CancelCode.MismatchedKeys)
}
@ -88,7 +88,7 @@ internal class QrCodeVerification(
override var state: VerificationTxState
get() {
refreshData()
val inner = this.inner
val inner = inner
val cancelInfo = inner?.cancelInfo
return if (inner != null) {
@ -114,22 +114,22 @@ internal class QrCodeVerification(
/** Get the unique id of this verification */
override val transactionId: String
get() = this.request.flowId()
get() = request.flowId()
/** Get the user id of the other user participating in this verification flow */
override val otherUserId: String
get() = this.request.otherUser()
get() = request.otherUser()
/** Get the device id of the other user's device participating in this verification flow */
override var otherDeviceId: String?
get() = this.request.otherDeviceId()
get() = request.otherDeviceId()
@Suppress("UNUSED_PARAMETER")
set(value) {
}
/** Did the other side initiate this verification flow */
override val isIncoming: Boolean
get() = !this.request.weStarted()
get() = !request.weStarted()
/** Cancel the verification flow
*
@ -140,7 +140,7 @@ internal class QrCodeVerification(
*
* The method turns into a noop, if the verification flow has already been cancelled.
* */
override fun cancel() {
override suspend fun cancel() {
cancelHelper(CancelCode.User)
}
@ -155,13 +155,13 @@ internal class QrCodeVerification(
*
* @param code The cancel code that should be given as the reason for the cancellation.
* */
override fun cancel(code: CancelCode) {
override suspend fun cancel(code: CancelCode) {
cancelHelper(code)
}
/** Is this verification happening over to-device messages */
override fun isToDeviceTransport(): Boolean {
return this.request.roomId() == null
return request.roomId() == null
}
/** Confirm the QR code verification
@ -174,36 +174,36 @@ internal class QrCodeVerification(
*/
@Throws(CryptoStoreException::class)
private suspend fun confirm() {
val result = withContext(Dispatchers.IO) {
val result = withContext(coroutineDispatchers.io) {
machine.confirmVerification(request.otherUser(), request.flowId())
}
if (result != null) {
this.sender.sendVerificationRequest(result.request)
dispatchTxUpdated()
val signatureRequest = result.signatureRequest
if (signatureRequest != null) {
this.sender.sendSignatureUpload(signatureRequest)
for (verificationRequest in result.requests) {
sender.sendVerificationRequest(verificationRequest)
}
val signatureRequest = result.signatureRequest
if (signatureRequest != null) {
sender.sendSignatureUpload(signatureRequest)
}
dispatchTxUpdated()
}
}
private fun cancelHelper(code: CancelCode) {
val request = this.machine.cancelVerification(this.request.otherUser(), this.request.flowId(), code.value)
private suspend fun cancelHelper(code: CancelCode) {
val request = machine.cancelVerification(request.otherUser(), request.flowId(), code.value)
if (request != null) {
runBlocking { sender.sendVerificationRequest(request) }
sender.sendVerificationRequest(request)
dispatchTxUpdated()
}
}
/** Fetch fresh data from the Rust side for our verification flow */
private fun refreshData() {
when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) {
when (val verification = machine.getVerification(request.otherUser(), request.flowId())) {
is Verification.QrCodeV1 -> {
this.inner = verification.qrcode
inner = verification.qrcode
}
else -> {
}

View file

@ -16,10 +16,7 @@
package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.extensions.orFalse
@ -31,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.extensions.foldToCallback
import javax.inject.Inject
internal class RustCrossSigningService @Inject constructor(
@ -45,30 +41,30 @@ internal class RustCrossSigningService @Inject constructor(
/**
* Is our own device signed by our own cross signing identity
*/
override fun isCrossSigningVerified(): Boolean {
return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) {
override suspend fun isCrossSigningVerified(): Boolean {
return when (val identity = olmMachine.getIdentity(olmMachine.userId())) {
is OwnUserIdentity -> identity.trustsOurOwnDevice()
else -> false
}
}
override fun isUserTrusted(otherUserId: String): Boolean {
override suspend fun isUserTrusted(otherUserId: String): Boolean {
// This seems to be used only in tests.
return this.checkUserTrust(otherUserId).isVerified()
return checkUserTrust(otherUserId).isVerified()
}
/**
* 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
*/
override fun checkUserTrust(otherUserId: String): UserTrustResult {
val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }
override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
val identity = olmMachine.getIdentity(olmMachine.userId())
// While UserTrustResult has many different states, they are by the callers
// converted to a boolean value immediately, thus we don't need to support
// all the values.
return if (identity != null) {
val verified = runBlocking { identity.verified() }
val verified = identity.verified()
if (verified) {
UserTrustResult.Success
@ -84,8 +80,8 @@ internal class RustCrossSigningService @Inject constructor(
* Initialize cross signing for this user.
* Users needs to enter credentials
*/
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) }
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
olmMachine.bootstrapCrossSigning(uiaInterceptor)
}
/**
@ -108,26 +104,26 @@ internal class RustCrossSigningService @Inject constructor(
*
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
*/
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() }
override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo()
}
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
return runBlocking { olmMachine.getLiveUserIdentity(userId) }
override fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return olmMachine.getLiveUserIdentity(userId)
}
/** Get our own public cross signing keys */
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return getUserCrossSigningKeys(olmMachine.userId())
}
/** Get our own private cross signing keys */
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return runBlocking { olmMachine.exportCrossSigningKeys() }
override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return olmMachine.exportCrossSigningKeys()
}
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() }
override fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
return olmMachine.getLivePrivateCrossSigningKeys()
}
/**
@ -136,34 +132,32 @@ internal class RustCrossSigningService @Inject constructor(
* Returning true means that we have the private self-signing and user-signing keys at hand.
*/
override fun canCrossSign(): Boolean {
val status = this.olmMachine.crossSigningStatus()
val status = olmMachine.crossSigningStatus()
return status.hasSelfSigning && status.hasUserSigning
}
override fun allPrivateKeysKnown(): Boolean {
val status = this.olmMachine.crossSigningStatus()
val status = olmMachine.crossSigningStatus()
return status.hasMaster && status.hasSelfSigning && status.hasUserSigning
}
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
override suspend fun trustUser(otherUserId: String) {
// This is only used in a test
val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) }
val userIdentity = olmMachine.getIdentity(otherUserId)
if (userIdentity != null) {
runBlocking { userIdentity.verify() }
callback.onSuccess(Unit)
userIdentity.verify()
} else {
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
}
}
/** Mark our own master key as trusted */
override fun markMyMasterKeyAsTrusted() {
override suspend fun markMyMasterKeyAsTrusted() {
// This doesn't seem to be used?
this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback())
trustUser(olmMachine.userId())
}
/**
@ -171,10 +165,8 @@ internal class RustCrossSigningService @Inject constructor(
*/
override suspend fun trustDevice(deviceId: String) {
val device = olmMachine.getDevice(olmMachine.userId(), deviceId)
return if (device != null) {
if (device != null) {
val verified = device.verify()
if (verified) {
return
} else {
@ -192,15 +184,15 @@ internal class RustCrossSigningService @Inject constructor(
* using the self-signing key for our own devices or using the user-signing key and the master
* key of another user.
*/
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) }
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val device = olmMachine.getDevice(otherUserId, otherDeviceId)
return if (device != null) {
// TODO i don't quite understand the semantics here and there are no docs for
// DeviceTrustResult, what do the different result types mean exactly,
// do you return success only if the Device is cross signing verified?
// what about the local trust if it isn't? why is the local trust passed as an argument here?
DeviceTrustResult.Success(runBlocking { device.trustLevel() })
DeviceTrustResult.Success(device.trustLevel())
} else {
DeviceTrustResult.UnknownDevice(otherDeviceId)
}
@ -215,7 +207,7 @@ internal class RustCrossSigningService @Inject constructor(
}
override fun onSecretUSKGossip(uskPrivateKey: String) {
// And this.
// And
}
override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {

View file

@ -16,15 +16,15 @@
package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
import uniffi.olm.CryptoStoreException
@ -37,6 +37,7 @@ internal class SasVerification(
private val machine: OlmMachine,
private var inner: Sas,
private val sender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
listeners: ArrayList<VerificationService.Listener>
) :
SasVerificationTransaction {
@ -44,27 +45,27 @@ internal class SasVerification(
private fun dispatchTxUpdated() {
refreshData()
this.dispatcher.dispatchTxUpdated(this)
dispatcher.dispatchTxUpdated(this)
}
/** The user ID of the other user that is participating in this verification flow */
override val otherUserId: String = this.inner.otherUserId
override val otherUserId: String = inner.otherUserId
/** Get the device id of the other user's device participating in this verification flow */
override var otherDeviceId: String?
get() = this.inner.otherDeviceId
get() = inner.otherDeviceId
@Suppress("UNUSED_PARAMETER")
set(value) {
}
/** Did the other side initiate this verification flow */
override val isIncoming: Boolean
get() = !this.inner.weStarted
get() = !inner.weStarted
override var state: VerificationTxState
get() {
refreshData()
val cancelInfo = this.inner.cancelInfo
val cancelInfo = inner.cancelInfo
return when {
cancelInfo != null -> {
@ -84,7 +85,7 @@ internal class SasVerification(
/** Get the unique id of this verification */
override val transactionId: String
get() = this.inner.flowId
get() = inner.flowId
/** Cancel the verification flow
*
@ -95,8 +96,8 @@ internal class SasVerification(
*
* The method turns into a noop, if the verification flow has already been cancelled.
* */
override fun cancel() {
this.cancelHelper(CancelCode.User)
override suspend fun cancel() {
cancelHelper(CancelCode.User)
}
/** Cancel the verification flow
@ -110,8 +111,8 @@ internal class SasVerification(
*
* @param code The cancel code that should be given as the reason for the cancellation.
* */
override fun cancel(code: CancelCode) {
this.cancelHelper(code)
override suspend fun cancel(code: CancelCode) {
cancelHelper(code)
}
/** Cancel the verification flow
@ -123,25 +124,17 @@ internal class SasVerification(
*
* The method turns into a noop, if the verification flow has already been cancelled.
*/
override fun shortCodeDoesNotMatch() {
this.cancelHelper(CancelCode.MismatchedSas)
override suspend fun shortCodeDoesNotMatch() {
cancelHelper(CancelCode.MismatchedSas)
}
/** Is this verification happening over to-device messages */
override fun isToDeviceTransport(): Boolean = this.inner.roomId == null
/** Does the verification flow support showing decimals as the short auth string */
override fun supportsDecimal(): Boolean {
// This is ignored anyways, throw it away?
// The spec also mandates that devices support at least decimal and
// the rust-sdk cancels if devices don't support it
return true
}
override fun isToDeviceTransport(): Boolean = inner.roomId == null
/** Does the verification flow support showing emojis as the short auth string */
override fun supportsEmoji(): Boolean {
refreshData()
return this.inner.supportsEmoji
return inner.supportsEmoji
}
/** Confirm that the short authentication code matches on both sides
@ -153,8 +146,8 @@ internal class SasVerification(
* This method is a noop if we're not yet in a presentable state, i.e. we didn't receive
* a m.key.verification.key event from the other side or we're cancelled.
*/
override fun userHasVerifiedShortCode() {
runBlocking { confirm() }
override suspend fun userHasVerifiedShortCode() {
confirm()
}
/** Accept the verification flow, signaling the other side that we do want to verify
@ -165,8 +158,8 @@ internal class SasVerification(
* This method is a noop if we send the start event out or if the verification has already
* been accepted.
*/
override fun acceptVerification() {
runBlocking { accept() }
override suspend fun acceptVerification() {
accept()
}
/** Get the decimal representation of the short auth string
@ -176,7 +169,7 @@ internal class SasVerification(
* in a presentable state.
*/
override fun getDecimalCodeRepresentation(): String {
val decimals = this.machine.getDecimals(this.inner.otherUserId, this.inner.flowId)
val decimals = machine.getDecimals(inner.otherUserId, inner.flowId)
return decimals?.joinToString(" ") ?: ""
}
@ -188,52 +181,51 @@ internal class SasVerification(
* state.
*/
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
val emojiIndex = this.machine.getEmojiIndex(this.inner.otherUserId, this.inner.flowId)
val emojiIndex = machine.getEmojiIndex(inner.otherUserId, inner.flowId)
return emojiIndex?.map { getEmojiForCode(it) } ?: listOf()
}
internal suspend fun accept() {
val request = this.machine.acceptSasVerification(this.inner.otherUserId, inner.flowId)
val request = machine.acceptSasVerification(inner.otherUserId, inner.flowId)
if (request != null) {
this.sender.sendVerificationRequest(request)
sender.sendVerificationRequest(request)
dispatchTxUpdated()
}
}
@Throws(CryptoStoreException::class)
private suspend fun confirm() {
val result = withContext(Dispatchers.IO) {
val result = withContext(coroutineDispatchers.io) {
machine.confirmVerification(inner.otherUserId, inner.flowId)
}
if (result != null) {
this.sender.sendVerificationRequest(result.request)
dispatchTxUpdated()
val signatureRequest = result.signatureRequest
if (signatureRequest != null) {
this.sender.sendSignatureUpload(signatureRequest)
for (verificationRequest in result.requests) {
sender.sendVerificationRequest(verificationRequest)
}
val signatureRequest = result.signatureRequest
if (signatureRequest != null) {
sender.sendSignatureUpload(signatureRequest)
}
dispatchTxUpdated()
}
}
private fun cancelHelper(code: CancelCode) {
val request = this.machine.cancelVerification(this.inner.otherUserId, inner.flowId, code.value)
private suspend fun cancelHelper(code: CancelCode) {
val request = machine.cancelVerification(inner.otherUserId, inner.flowId, code.value)
if (request != null) {
runBlocking { sender.sendVerificationRequest(request) }
sender.sendVerificationRequest(request)
dispatchTxUpdated()
}
}
/** Fetch fresh data from the Rust side for our verification flow */
private fun refreshData() {
when (val verification = this.machine.getVerification(this.inner.otherUserId, this.inner.flowId)) {
when (val verification = machine.getVerification(inner.otherUserId, inner.flowId)) {
is Verification.SasV1 -> {
this.inner = verification.sas
inner = verification.sas
}
else -> {
}

View file

@ -16,14 +16,14 @@
package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreException
import uniffi.olm.SignatureException
@ -65,7 +65,7 @@ sealed class UserIdentities {
/**
* Convert the identity into a MxCrossSigningInfo class.
*/
abstract fun toMxCrossSigningInfo(): MXCrossSigningInfo
abstract suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo
}
/**
@ -80,11 +80,12 @@ internal class OwnUserIdentity(
private val userSigningKey: CryptoCrossSigningKey,
private val trustsOurOwnDevice: Boolean,
private val olmMachine: OlmMachine,
private val requestSender: RequestSender) : UserIdentities() {
private val requestSender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
/**
* Our own user id.
*/
override fun userId() = this.userId
override fun userId() = userId
/**
* Manually verify our user identity.
@ -95,8 +96,8 @@ internal class OwnUserIdentity(
*/
@Throws(SignatureException::class)
override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request)
val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
requestSender.sendSignatureUpload(request)
}
/**
@ -106,13 +107,13 @@ internal class OwnUserIdentity(
*/
@Throws(CryptoStoreException::class)
override suspend fun verified(): Boolean {
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
return withContext(coroutineDispatchers.io) { olmMachine.inner().isIdentityVerified(userId) }
}
/**
* Does the identity trust our own device.
*/
fun trustsOurOwnDevice() = this.trustsOurOwnDevice
fun trustsOurOwnDevice() = trustsOurOwnDevice
/**
* Request an interactive verification to begin
@ -133,32 +134,33 @@ internal class OwnUserIdentity(
@Throws(CryptoStoreException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
val stringMethods = prepareMethods(methods)
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
this.requestSender.sendVerificationRequest(result!!.request)
val result = olmMachine.inner().requestSelfVerification(stringMethods)
requestSender.sendVerificationRequest(result!!.request)
return VerificationRequest(
this.olmMachine.inner(),
result.verification,
this.requestSender,
this.olmMachine.verificationListeners
machine = olmMachine.inner(),
inner = result.verification,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = olmMachine.verificationListeners
)
}
/**
* Convert the identity into a MxCrossSigningInfo class.
*/
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
val masterKey = this.masterKey
val selfSigningKey = this.selfSigningKey
val userSigningKey = this.userSigningKey
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
val masterKey = masterKey
val selfSigningKey = selfSigningKey
val userSigningKey = userSigningKey
val trustLevel = DeviceTrustLevel(verified(), false)
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
masterKey.trustLevel = trustLevel
selfSigningKey.trustLevel = trustLevel
userSigningKey.trustLevel = trustLevel
val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey)
return MXCrossSigningInfo(this.userId, crossSigningKeys)
return MXCrossSigningInfo(userId, crossSigningKeys)
}
}
@ -172,11 +174,12 @@ internal class UserIdentity(
private val masterKey: CryptoCrossSigningKey,
private val selfSigningKey: CryptoCrossSigningKey,
private val olmMachine: OlmMachine,
private val requestSender: RequestSender) : UserIdentities() {
private val requestSender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
/**
* The unique ID of the user that this identity belongs to.
*/
override fun userId() = this.userId
override fun userId() = userId
/**
* Manually verify this user identity.
@ -189,8 +192,8 @@ internal class UserIdentity(
*/
@Throws(SignatureException::class)
override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request)
val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
requestSender.sendSignatureUpload(request)
}
/**
@ -199,7 +202,7 @@ internal class UserIdentity(
* @return True if the identity is considered to be verified and trusted, false otherwise.
*/
override suspend fun verified(): Boolean {
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
return withContext(coroutineDispatchers.io) { olmMachine.inner().isIdentityVerified(userId) }
}
/**
@ -232,32 +235,33 @@ internal class UserIdentity(
transactionId: String
): VerificationRequest {
val stringMethods = prepareMethods(methods)
val content = this.olmMachine.inner().verificationRequestContent(this.userId, stringMethods)!!
val content = olmMachine.inner().verificationRequestContent(userId, stringMethods)!!
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId)
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId).eventId
val innerRequest = this.olmMachine.inner().requestVerification(this.userId, roomId, eventID, stringMethods)!!
val innerRequest = olmMachine.inner().requestVerification(userId, roomId, eventID, stringMethods)!!
return VerificationRequest(
this.olmMachine.inner(),
innerRequest,
this.requestSender,
this.olmMachine.verificationListeners
machine = olmMachine.inner(),
inner = innerRequest,
sender = requestSender,
coroutineDispatchers = coroutineDispatchers,
listeners = olmMachine.verificationListeners
)
}
/**
* Convert the identity into a MxCrossSigningInfo class.
*/
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
// val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey)
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
// val crossSigningKeys = listOf(masterKey, selfSigningKey)
val trustLevel = DeviceTrustLevel(verified(), false)
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
masterKey.trustLevel = trustLevel
selfSigningKey.trustLevel = trustLevel
return MXCrossSigningInfo(this.userId, listOf(
this.masterKey.also { it.trustLevel = trustLevel },
this.selfSigningKey.also { it.trustLevel = trustLevel }
return MXCrossSigningInfo(userId, listOf(
masterKey.also { it.trustLevel = trustLevel },
selfSigningKey.also { it.trustLevel = trustLevel }
))
}
}

View file

@ -18,8 +18,8 @@ package org.matrix.android.sdk.internal.crypto
import android.os.Handler
import android.os.Looper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import timber.log.Timber
import uniffi.olm.OlmMachine
@ -45,6 +46,7 @@ internal class VerificationRequest(
private val machine: OlmMachine,
private var inner: VerificationRequest,
private val sender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val listeners: ArrayList<VerificationService.Listener>
) {
private val uiHandler = Handler(Looper.getMainLooper())
@ -53,7 +55,7 @@ internal class VerificationRequest(
uiHandler.post {
listeners.forEach {
try {
it.verificationRequestUpdated(this.toPendingVerificationRequest())
it.verificationRequestUpdated(toPendingVerificationRequest())
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
}
@ -68,12 +70,12 @@ internal class VerificationRequest(
* event that initiated the flow.
*/
internal fun flowId(): String {
return this.inner.flowId
return inner.flowId
}
/** The user ID of the other user that is participating in this verification flow */
internal fun otherUser(): String {
return this.inner.otherUserId
return inner.otherUserId
}
/** The device ID of the other user's device that is participating in this verification flow
@ -83,12 +85,12 @@ internal class VerificationRequest(
* */
internal fun otherDeviceId(): String? {
refreshData()
return this.inner.otherDeviceId
return inner.otherDeviceId
}
/** Did we initiate this verification flow */
internal fun weStarted(): Boolean {
return this.inner.weStarted
return inner.weStarted
}
/** Get the id of the room where this verification is happening
@ -96,7 +98,7 @@ internal class VerificationRequest(
* Will be null if the verification is not happening inside a room.
*/
internal fun roomId(): String? {
return this.inner.roomId
return inner.roomId
}
/** Did the non-initiating side respond with a m.key.verification.read event
@ -107,13 +109,13 @@ internal class VerificationRequest(
*/
internal fun isReady(): Boolean {
refreshData()
return this.inner.isReady
return inner.isReady
}
/** Did we advertise that we're able to scan QR codes */
internal fun canScanQrCodes(): Boolean {
refreshData()
return this.inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
return inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
}
/** Accept the verification request advertising the given methods as supported
@ -132,15 +134,15 @@ internal class VerificationRequest(
suspend fun acceptWithMethods(methods: List<VerificationMethod>) {
val stringMethods = prepareMethods(methods)
val request = this.machine.acceptVerificationRequest(
this.inner.otherUserId,
this.inner.flowId,
val request = machine.acceptVerificationRequest(
inner.otherUserId,
inner.flowId,
stringMethods
)
if (request != null) {
this.sender.sendVerificationRequest(request)
this.dispatchRequestUpdated()
sender.sendVerificationRequest(request)
dispatchRequestUpdated()
}
}
@ -158,12 +160,12 @@ internal class VerificationRequest(
* emoji verification, or null if we can't yet transition into emoji verification.
*/
internal suspend fun startSasVerification(): SasVerification? {
return withContext(Dispatchers.IO) {
return withContext(coroutineDispatchers.io) {
val result = machine.startSasVerification(inner.otherUserId, inner.flowId)
if (result != null) {
sender.sendVerificationRequest(result.request)
SasVerification(machine, result.sas, sender, listeners)
SasVerification(machine, result.sas, sender, coroutineDispatchers, listeners)
} else {
null
}
@ -187,10 +189,10 @@ internal class VerificationRequest(
// TODO again, what's the deal with ISO_8859_1?
val byteArray = data.toByteArray(Charsets.ISO_8859_1)
val encodedData = byteArray.toBase64NoPadding()
val result = this.machine.scanQrCode(this.otherUser(), this.flowId(), encodedData) ?: return null
val result = machine.scanQrCode(otherUser(), flowId(), encodedData) ?: return null
this.sender.sendVerificationRequest(result.request)
return QrCodeVerification(this.machine, this, result.qr, this.sender, this.listeners)
sender.sendVerificationRequest(result.request)
return QrCodeVerification(machine, this, result.qr, sender, coroutineDispatchers, listeners)
}
/** Transition into a QR code verification to display a QR code
@ -211,15 +213,16 @@ internal class VerificationRequest(
* QR code verification, or null if we can't yet transition into QR code verification.
*/
internal fun startQrVerification(): QrCodeVerification? {
val qrcode = this.machine.startQrVerification(this.inner.otherUserId, this.inner.flowId)
val qrcode = machine.startQrVerification(inner.otherUserId, inner.flowId)
return if (qrcode != null) {
QrCodeVerification(
this.machine,
this,
qrcode,
this.sender,
this.listeners,
machine = machine,
request = this,
inner = qrcode,
sender = sender,
coroutineDispatchers = coroutineDispatchers,
listeners = listeners,
)
} else {
null
@ -237,24 +240,24 @@ internal class VerificationRequest(
* The method turns into a noop, if the verification flow has already been cancelled.
*/
internal suspend fun cancel() {
val request = this.machine.cancelVerification(
this.inner.otherUserId,
this.inner.flowId,
val request = machine.cancelVerification(
inner.otherUserId,
inner.flowId,
CancelCode.User.value
)
if (request != null) {
this.sender.sendVerificationRequest(request)
this.dispatchRequestUpdated()
sender.sendVerificationRequest(request)
dispatchRequestUpdated()
}
}
/** Fetch fresh data from the Rust side for our verification flow */
private fun refreshData() {
val request = this.machine.getVerificationRequest(this.inner.otherUserId, this.inner.flowId)
val request = machine.getVerificationRequest(inner.otherUserId, inner.flowId)
if (request != null) {
this.inner = request
inner = request
}
}
@ -269,7 +272,7 @@ internal class VerificationRequest(
*/
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
refreshData()
val cancelInfo = this.inner.cancelInfo
val cancelInfo = inner.cancelInfo
val cancelCode =
if (cancelInfo != null) {
safeValueOf(cancelInfo.cancelCode)
@ -277,72 +280,72 @@ internal class VerificationRequest(
null
}
val ourMethods = this.inner.ourMethods
val theirMethods = this.inner.theirMethods
val otherDeviceId = this.inner.otherDeviceId
val ourMethods = inner.ourMethods
val theirMethods = inner.theirMethods
val otherDeviceId = inner.otherDeviceId
var requestInfo: ValidVerificationInfoRequest? = null
var readyInfo: ValidVerificationInfoReady? = null
if (this.inner.weStarted && ourMethods != null) {
if (inner.weStarted && ourMethods != null) {
requestInfo =
ValidVerificationInfoRequest(
this.inner.flowId,
this.machine.deviceId(),
ourMethods,
null,
transactionId = inner.flowId,
fromDevice = machine.deviceId(),
methods = ourMethods,
timestamp = null,
)
} else if (!this.inner.weStarted && ourMethods != null) {
} else if (!inner.weStarted && ourMethods != null) {
readyInfo =
ValidVerificationInfoReady(
this.inner.flowId,
this.machine.deviceId(),
ourMethods,
transactionId = inner.flowId,
fromDevice = machine.deviceId(),
methods = ourMethods,
)
}
if (this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
if (inner.weStarted && theirMethods != null && otherDeviceId != null) {
readyInfo =
ValidVerificationInfoReady(
this.inner.flowId,
otherDeviceId,
theirMethods,
transactionId = inner.flowId,
fromDevice = otherDeviceId,
methods = theirMethods,
)
} else if (!this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
} else if (!inner.weStarted && theirMethods != null && otherDeviceId != null) {
requestInfo =
ValidVerificationInfoRequest(
this.inner.flowId,
otherDeviceId,
theirMethods,
System.currentTimeMillis(),
transactionId = inner.flowId,
fromDevice = otherDeviceId,
methods = theirMethods,
timestamp = System.currentTimeMillis(),
)
}
return PendingVerificationRequest(
// Creation time
System.currentTimeMillis(),
ageLocalTs = System.currentTimeMillis(),
// Who initiated the request
!this.inner.weStarted,
isIncoming = !inner.weStarted,
// Local echo id, what to do here?
this.inner.flowId,
localId = inner.flowId,
// other user
this.inner.otherUserId,
otherUserId = inner.otherUserId,
// room id
this.inner.roomId,
roomId = inner.roomId,
// transaction id
this.inner.flowId,
transactionId = inner.flowId,
// val requestInfo: ValidVerificationInfoRequest? = null,
requestInfo,
requestInfo = requestInfo,
// val readyInfo: ValidVerificationInfoReady? = null,
readyInfo,
readyInfo = readyInfo,
// cancel code if there is one
cancelCode,
cancelConclusion = cancelCode,
// are we done/successful
this.inner.isDone,
isSuccessful = inner.isDone,
// did another device answer the request
this.inner.isPassive,
handledByOtherSession = inner.isPassive,
// devices that should receive the events we send out
null,
targetDevices = null
)
}
}

View file

@ -21,7 +21,6 @@ import android.os.Looper
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -41,7 +40,6 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
import org.matrix.android.sdk.internal.crypto.RequestSender
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
@ -53,6 +51,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.SessionScope
@ -95,7 +94,7 @@ internal class RustKeyBackupService @Inject constructor(
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val importScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.main)
private var keysBackupStateListener: KeysBackupStateListener? = null
@ -237,7 +236,7 @@ internal class RustKeyBackupService @Inject constructor(
}
}
override fun canRestoreKeys(): Boolean {
override suspend fun canRestoreKeys(): Boolean {
val keyCountOnServer = keysBackupVersion?.count ?: return false
val keyCountLocally = getTotalNumbersOfKeys()
@ -246,11 +245,11 @@ internal class RustKeyBackupService @Inject constructor(
return keyCountLocally < keyCountOnServer
}
override fun getTotalNumbersOfKeys(): Int {
override suspend fun getTotalNumbersOfKeys(): Int {
return olmMachine.roomKeyCounts().total.toInt()
}
override fun getTotalNumbersOfBackedUpKeys(): Int {
override suspend fun getTotalNumbersOfBackedUpKeys(): Int {
return olmMachine.roomKeyCounts().backedUp.toInt()
}
@ -405,7 +404,7 @@ internal class RustKeyBackupService @Inject constructor(
}
}
override fun getBackupProgress(progressListener: ProgressListener) {
override suspend fun getBackupProgress(progressListener: ProgressListener) {
val backedUpKeys = getTotalNumbersOfBackedUpKeys()
val total = getTotalNumbersOfKeys()
@ -490,7 +489,7 @@ internal class RustKeyBackupService @Inject constructor(
val data = getKeys(sessionId, roomId, keysVersionResult.version)
return withContext(coroutineDispatchers.computation) {
withContext(Dispatchers.Main) {
withContext(coroutineDispatchers.main) {
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
}
// Decrypting by chunk of 500 keys in parallel
@ -513,7 +512,7 @@ internal class RustKeyBackupService @Inject constructor(
.awaitAll()
.flatten()
withContext(Dispatchers.Main) {
withContext(coroutineDispatchers.main) {
val stepProgress = StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size)
stepProgressListener?.onStepProgress(stepProgress)
}
@ -532,7 +531,7 @@ internal class RustKeyBackupService @Inject constructor(
val progressListener = if (stepProgressListener != null) {
object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
cryptoCoroutineScope.launch(Dispatchers.Main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val stepProgress = StepProgressListener.Step.ImportingKey(progress, total)
stepProgressListener.onStepProgress(stepProgress)
}
@ -581,16 +580,12 @@ internal class RustKeyBackupService @Inject constructor(
}
override suspend fun getVersion(version: String): KeysVersionResult? {
return withContext(coroutineDispatchers.io) {
sender.getKeyBackupVersion(version)
}
return sender.getKeyBackupVersion(version)
}
@Throws
override suspend fun getCurrentVersion(): KeysVersionResult? {
return withContext(coroutineDispatchers.io) {
sender.getKeyBackupVersion()
}
return sender.getKeyBackupVersion()
}
override suspend fun forceUsingLastVersion(): Boolean {
@ -646,18 +641,16 @@ internal class RustKeyBackupService @Inject constructor(
Timber.w("checkAndStartKeysBackup: invalid state: $state")
return@withContext
}
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
withContext(coroutineDispatchers.io) {
try {
val data = getCurrentVersion()
withContext(coroutineDispatchers.crypto) {
checkAndStartWithKeysBackupVersion(data)
}
} catch (failure: Throwable) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
try {
val data = getCurrentVersion()
withContext(coroutineDispatchers.crypto) {
checkAndStartWithKeysBackupVersion(data)
}
} catch (failure: Throwable) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
withContext(coroutineDispatchers.crypto) {
keysBackupStateManager.state = KeysBackupState.Unknown
}
}
@ -725,7 +718,7 @@ internal class RustKeyBackupService @Inject constructor(
}
}
override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
val info = olmMachine.getBackupKeys() ?: return null
return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
}
@ -878,7 +871,7 @@ internal class RustKeyBackupService @Inject constructor(
}
} catch (failure: Throwable) {
if (failure is Failure.ServerError) {
withContext(Dispatchers.Main) {
withContext(coroutineDispatchers.main) {
Timber.e(failure, "backupKeys: backupKeys failed.")
when (failure.error.code) {

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.crypto.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
import org.matrix.android.sdk.internal.crypto.OlmMachine
import timber.log.Timber
import uniffi.olm.Request
import uniffi.olm.RequestType
private val loggerTag = LoggerTag("OutgoingRequestsProcessor", LoggerTag.CRYPTO)
internal class OutgoingRequestsProcessor(private val requestSender: RequestSender,
private val coroutineScope: CoroutineScope,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val shieldComputer: ShieldComputer) {
fun interface ShieldComputer {
suspend fun compute(userIds: List<String>): RoomEncryptionTrustLevel
}
private val lock: Mutex = Mutex()
suspend fun process(olmMachine: OlmMachine) {
lock.withLock {
coroutineScope {
Timber.v("OutgoingRequests: ${olmMachine.outgoingRequests()}")
olmMachine.outgoingRequests().map {
when (it) {
is Request.KeysUpload -> {
async {
uploadKeys(olmMachine, it)
}
}
is Request.KeysQuery -> {
async {
queryKeys(olmMachine, it)
}
}
is Request.ToDevice -> {
async {
sendToDevice(olmMachine, it)
}
}
is Request.KeysClaim -> {
async {
claimKeys(olmMachine, it)
}
}
is Request.RoomMessage -> {
async {
sendRoomMessage(olmMachine, it)
}
}
is Request.SignatureUpload -> {
async {
signatureUpload(olmMachine, it)
}
}
is Request.KeysBackup -> {
async {
// The rust-sdk won't ever produce KeysBackup requests here,
// those only get explicitly created.
}
}
}
}.joinAll()
}
}
}
private suspend fun uploadKeys(olmMachine: OlmMachine, request: Request.KeysUpload) {
try {
val response = requestSender.uploadKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## uploadKeys(): error")
}
}
private suspend fun queryKeys(olmMachine: OlmMachine, request: Request.KeysQuery) {
try {
val response = requestSender.queryKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
coroutineScope.updateShields(request.users)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## queryKeys(): error")
}
}
private fun CoroutineScope.updateShields(userIds: List<String>) = launch {
cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(userIds).forEach { roomId ->
val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId)
val shield = shieldComputer.compute(userGroup)
cryptoSessionInfoProvider.updateShieldForRoom(roomId, shield)
}
}
private suspend fun sendToDevice(olmMachine: OlmMachine, request: Request.ToDevice) {
try {
requestSender.sendToDevice(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## sendToDevice(): error")
}
}
private suspend fun claimKeys(olmMachine: OlmMachine, request: Request.KeysClaim) {
try {
val response = requestSender.claimKeys(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## claimKeys(): error")
}
}
private suspend fun signatureUpload(olmMachine: OlmMachine, request: Request.SignatureUpload) {
try {
val response = requestSender.sendSignatureUpload(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## signatureUpload(): error")
}
}
private suspend fun sendRoomMessage(olmMachine: OlmMachine, request: Request.RoomMessage) {
try {
val response = requestSender.sendRoomMessage(request)
olmMachine.markRequestAsSent(request.requestId, RequestType.ROOM_MESSAGE, response)
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "## sendRoomMessage(): error")
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto
package org.matrix.android.sdk.internal.crypto.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import dagger.Lazy
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -46,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
@ -56,6 +58,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
import org.matrix.android.sdk.internal.session.room.send.SendResponse
import timber.log.Timber
import uniffi.olm.OutgoingVerificationRequest
import uniffi.olm.Request
@ -80,6 +83,7 @@ internal class RequestSender @Inject constructor(
private val getSessionsDataTask: GetSessionsDataTask,
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
private val getRoomSessionDataTask: GetRoomSessionDataTask,
private val moshi: Moshi
) {
companion object {
const val REQUEST_RETRY_COUNT = 3
@ -97,16 +101,16 @@ internal class RequestSender @Inject constructor(
suspend fun queryKeys(request: Request.KeysQuery): String {
val params = DownloadKeysForUsersTask.Params(request.users, null)
val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT)
val adapter = MoshiProvider.providesMoshi().adapter(KeysQueryResponse::class.java)
val adapter = moshi.adapter(KeysQueryResponse::class.java)
return adapter.toJson(response)!!
}
suspend fun uploadKeys(request: Request.KeysUpload): String {
val body = MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java).fromJson(request.body)!!
val body = moshi.adapter<JsonDict>(Map::class.java).fromJson(request.body)!!
val params = UploadKeysTask.Params(body)
val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
val adapter = MoshiProvider.providesMoshi().adapter(KeysUploadResponse::class.java)
val adapter = moshi.adapter(KeysUploadResponse::class.java)
return adapter.toJson(response)!!
}
@ -118,42 +122,46 @@ internal class RequestSender @Inject constructor(
}
}
suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): String {
private suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): SendResponse {
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
}
suspend fun sendRoomMessage(request: Request.RoomMessage): String {
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
val sendResponse = sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
val responseAdapter = moshi.adapter(SendResponse::class.java)
return responseAdapter.toJson(sendResponse)
}
suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): String {
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
val jsonContent = adapter.fromJson(content)
suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): SendResponse {
val paramsAdapter = moshi.adapter<Content>(Map::class.java)
val jsonContent = paramsAdapter.fromJson(content)
val event = Event(eventType, transactionId, jsonContent, roomId = roomId)
val params = SendVerificationMessageTask.Params(event)
return this.sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT)
return sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT)
}
suspend fun sendSignatureUpload(request: Request.SignatureUpload) {
sendSignatureUpload(request.body)
suspend fun sendSignatureUpload(request: Request.SignatureUpload): String {
return sendSignatureUpload(request.body)
}
suspend fun sendSignatureUpload(request: SignatureUploadRequest) {
sendSignatureUpload(request.body)
suspend fun sendSignatureUpload(request: SignatureUploadRequest): String {
return sendSignatureUpload(request.body)
}
private suspend fun sendSignatureUpload(body: String) {
val adapter = MoshiProvider.providesMoshi().adapter<Map<String, Map<String, Any>>>(Map::class.java)
val signatures = adapter.fromJson(body)!!
private suspend fun sendSignatureUpload(body: String): String {
val paramsAdapter = moshi.adapter<Map<String, Map<String, Any>>>(Map::class.java)
val signatures = paramsAdapter.fromJson(body)!!
val params = UploadSignaturesTask.Params(signatures)
this.signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT)
val response = signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT)
val responseAdapter = moshi.adapter(SignatureUploadResponse::class.java)
return responseAdapter.toJson(response)!!
}
suspend fun uploadCrossSigningKeys(
request: UploadSigningKeysRequest,
interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
) {
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
val adapter = moshi.adapter(RestKeyInfo::class.java)
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
@ -195,8 +203,7 @@ internal class RequestSender @Inject constructor(
}
suspend fun sendToDevice(eventType: String, body: String, transactionId: String) {
val adapter = MoshiProvider
.providesMoshi()
val adapter = moshi
.newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build()
@ -252,7 +259,7 @@ internal class RequestSender @Inject constructor(
val keys = adapter.fromJson(request.rooms)!!
val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys))
val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java)
val responseAdapter = moshi.adapter(BackupKeysResult::class.java)
return responseAdapter.toJson(response)!!
}

View file

@ -22,18 +22,14 @@ import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitCallback
import javax.inject.Inject
internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
data class Params(val roomId: String,
val event: Event,
/**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
val keepKeys: List<String>? = null
val event: Event
)
}
@ -51,52 +47,34 @@ internal class DefaultEncryptEventTask @Inject constructor(
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
params.keepKeys?.forEach {
localMutableContent.remove(it)
}
// try {
// let it throws
awaitCallback<MXEncryptEventContentResult> {
cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
}.let { result ->
val modifiedContent = HashMap(result.eventContent)
params.keepKeys?.forEach { toKeep ->
localEvent.content?.get(toKeep)?.let {
// put it back in the encrypted thing
modifiedContent[toKeep] = it
}
}
val safeResult = result.copy(eventContent = modifiedContent)
// Better handling of local echo, to avoid decrypting transition on remote echo
// Should I only do it for text messages?
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
MXEventDecryptionResult(
clearEvent = Event(
type = localEvent.type,
content = localEvent.content,
roomId = localEvent.roomId
).toContent(),
forwardingCurve25519KeyChain = emptyList(),
senderCurve25519Key = result.eventContent["sender_key"] as? String,
claimedEd25519Key = cryptoService.getMyDevice().fingerprint()
)
} else {
null
}
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
localEcho.type = EventType.ENCRYPTED
localEcho.content = ContentMapper.map(modifiedContent)
decryptionLocalEcho?.also {
localEcho.setDecryptionResult(it)
}
}
return localEvent.copy(
type = safeResult.eventType,
content = safeResult.eventContent
val result = cryptoService.encryptEventContent(localEvent.content ?: emptyMap(), localEvent.type, params.roomId)
// Better handling of local echo, to avoid decrypting transition on remote echo
// Should I only do it for text messages?
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
MXEventDecryptionResult(
clearEvent = Event(
type = localEvent.type,
content = localEvent.content,
roomId = localEvent.roomId
).toContent(),
forwardingCurve25519KeyChain = emptyList(),
senderCurve25519Key = result.eventContent["sender_key"] as? String,
claimedEd25519Key = cryptoService.getMyCryptoDevice().fingerprint()
)
} else {
null
}
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
localEcho.type = EventType.ENCRYPTED
localEcho.content = ContentMapper.map(result.eventContent)
decryptionLocalEcho?.also {
localEcho.setDecryptionResult(it)
}
}
return localEvent.copy(
type = result.eventType,
content = result.eventContent
)
}
}

View file

@ -76,8 +76,7 @@ internal class DefaultSendEventTask @Inject constructor(
if (params.encrypt && !params.event.isEncrypted()) {
return encryptEventTask.execute(EncryptEventTask.Params(
params.event.roomId ?: "",
params.event,
listOf("m.relates_to")
params.event
))
}
return params.event

View file

@ -22,11 +22,12 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.SendResponse
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.toMatrixErrorStr
import javax.inject.Inject
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
data class Params(
val event: Event
)
@ -39,10 +40,9 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse {
val event = handleEncryption(params)
val localId = event.eventId!!
try {
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
val response = executeRequest(globalErrorReceiver) {
@ -54,7 +54,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
)
}
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
return response.eventId
return response
} catch (e: Throwable) {
localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr())
throw e
@ -67,7 +67,6 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
return encryptEventTask.execute(EncryptEventTask.Params(
params.event.roomId ?: "",
params.event,
listOf("m.relates_to")
))
} catch (throwable: Throwable) {
// We said it's ok to send verification request in clear

View file

@ -15,14 +15,14 @@
*/
package org.matrix.android.sdk.internal.crypto.tasks
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, Unit> {
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, SignatureUploadResponse> {
data class Params(
val signatures: Map<String, Map<String, Any>>
)
@ -33,21 +33,13 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
private val globalErrorReceiver: GlobalErrorReceiver
) : UploadSignaturesTask {
override suspend fun execute(params: UploadSignaturesTask.Params) {
try {
val response = executeRequest(
globalErrorReceiver,
canRetry = true,
maxRetriesCount = 10
) {
cryptoApi.uploadSignatures(params.signatures)
}
if (response.failures?.isNotEmpty() == true) {
throw Throwable(response.failures.toString())
}
return
} catch (f: Failure) {
throw f
override suspend fun execute(params: UploadSignaturesTask.Params): SignatureUploadResponse {
return executeRequest(
globalErrorReceiver,
canRetry = true,
maxRetriesCount = 10
) {
cryptoApi.uploadSignatures(params.signatures)
}
}
}

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.verification
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -26,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
import org.matrix.android.sdk.api.session.events.model.Event
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.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
@ -77,31 +77,41 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
olmMachineProvider.olmMachine
}
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
private val dispatcher = UpdateDispatcher(olmMachine.verificationListeners)
/** The main entry point for the verification service
/**
*
* All verification related events should be forwarded through this method to
* the verification service.
*
* Since events are at this point already handled by the rust-sdk through the receival
* of the to-device events and the decryption of room events, this method mainly just
* If the verification event is not encrypted it should be provided to the olmMachine.
* Otherwise events are at this point already handled by the rust-sdk through the receival
* of the to-device events and the decryption of room events. In this case this method mainly just
* fetches the appropriate rust object that will be created or updated by the event and
* dispatches updates to our listeners.
*/
internal suspend fun onEvent(event: Event) = when (event.getClearType()) {
// I'm not entirely sure why getClearType() returns a msgtype in one case
// and a event type in the other case, but this is how the old verification
// service did things and it does seem to work.
MessageType.MSGTYPE_VERIFICATION_REQUEST -> onRequest(event)
EventType.KEY_VERIFICATION_START -> onStart(event)
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_MAC,
EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_DONE -> onUpdate(event)
else -> {
internal suspend fun onEvent(roomId: String?, event: Event) {
if (roomId != null && !event.isEncrypted()) {
olmMachine.receiveUnencryptedVerificationEvent(roomId, event)
}
when (event.getClearType()) {
EventType.KEY_VERIFICATION_REQUEST -> onRequest(event, fromRoomMessage = false)
EventType.KEY_VERIFICATION_START -> onStart(event)
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_MAC,
EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_DONE -> onUpdate(event)
EventType.MESSAGE -> onRoomMessage(event)
else -> Unit
}
}
private fun onRoomMessage(event: Event) {
val messageContent = event.getClearContent()?.toModel<MessageContent>() ?: return
if (messageContent.msgType == MessageType.MSGTYPE_VERIFICATION_REQUEST) {
onRequest(event, fromRoomMessage = true)
}
}
@ -110,9 +120,9 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
val sender = event.senderId ?: return
val flowId = getFlowId(event) ?: return
this.olmMachine.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated()
val verification = this.getExistingTransaction(sender, flowId) ?: return
this.dispatcher.dispatchTxUpdated(verification)
olmMachine.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated()
val verification = getExistingTransaction(sender, flowId) ?: return
dispatcher.dispatchTxUpdated(verification)
}
/** Check if the start event created new verification objects and dispatch updates */
@ -120,8 +130,8 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
val sender = event.senderId ?: return
val flowId = getFlowId(event) ?: return
val verification = this.getExistingTransaction(sender, flowId) ?: return
val request = this.olmMachine.getVerificationRequest(sender, flowId)
val verification = getExistingTransaction(sender, flowId) ?: return
val request = olmMachine.getVerificationRequest(sender, flowId)
if (request != null && request.isReady()) {
// If this is a SAS verification originating from a `m.key.verification.request`
@ -132,59 +142,54 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
Timber.d("## Verification: Auto accepting SAS verification with $sender")
verification.accept()
} else {
this.dispatcher.dispatchTxUpdated(verification)
dispatcher.dispatchTxUpdated(verification)
}
} else {
// This didn't originate from a request, so tell our listeners that
// this is a new verification.
this.dispatcher.dispatchTxAdded(verification)
dispatcher.dispatchTxAdded(verification)
// The IncomingVerificationRequestHandler seems to only listen to updates
// so let's trigger an update after the addition as well.
this.dispatcher.dispatchTxUpdated(verification)
dispatcher.dispatchTxUpdated(verification)
}
}
/** Check if the request event created a nev verification request object and dispatch that it dis so */
private fun onRequest(event: Event) {
val flowId = getFlowId(event) ?: return
private fun onRequest(event: Event, fromRoomMessage: Boolean) {
val flowId = if (fromRoomMessage) {
event.eventId
} else {
event.getClearContent().toModel<ToDeviceVerificationEvent>()?.transactionId
} ?: return
val sender = event.senderId ?: return
val request = getExistingVerificationRequest(sender, flowId) ?: return
val request = this.getExistingVerificationRequest(sender, flowId) ?: return
this.dispatcher.dispatchRequestAdded(request)
dispatcher.dispatchRequestAdded(request)
}
override fun addListener(listener: VerificationService.Listener) {
this.dispatcher.addListener(listener)
dispatcher.addListener(listener)
}
override fun removeListener(listener: VerificationService.Listener) {
this.dispatcher.removeListener(listener)
dispatcher.removeListener(listener)
}
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
// TODO this doesn't seem to be used anymore?
runBlocking {
val device = olmMachine.getDevice(userId, deviceID)
device?.markAsTrusted()
}
}
override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) {
// TODO This should be handled inside the rust-sdk decryption method
override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
olmMachine.getDevice(userId, deviceID)?.markAsTrusted()
}
override fun getExistingTransaction(
otherUserId: String,
tid: String,
): VerificationTransaction? {
return this.olmMachine.getVerification(otherUserId, tid)
return olmMachine.getVerification(otherUserId, tid)
}
override fun getExistingVerificationRequests(
otherUserId: String
): List<PendingVerificationRequest> {
return this.olmMachine.getVerificationRequests(otherUserId).map {
return olmMachine.getVerificationRequests(otherUserId).map {
it.toPendingVerificationRequest()
}
}
@ -194,7 +199,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
tid: String?
): PendingVerificationRequest? {
return if (tid != null) {
this.olmMachine.getVerificationRequest(otherUserId, tid)?.toPendingVerificationRequest()
olmMachine.getVerificationRequest(otherUserId, tid)?.toPendingVerificationRequest()
} else {
null
}
@ -224,29 +229,24 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return null
}
override fun requestKeyVerification(
methods: List<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?
): PendingVerificationRequest {
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
override suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest {
val verification = when (val identity = olmMachine.getIdentity(olmMachine.userId())) {
is OwnUserIdentity -> identity.requestVerification(methods)
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
}
return verification.toPendingVerificationRequest()
}
override fun requestKeyVerificationInDMs(
override suspend fun requestKeyVerificationInDMs(
methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
localId: String?
): PendingVerificationRequest {
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
olmMachine.ensureUsersKeys(listOf(otherUserId))
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
}
@ -254,21 +254,20 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
return verification.toPendingVerificationRequest()
}
override fun readyPendingVerification(
override suspend fun readyPendingVerification(
methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String
): Boolean {
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
val request = olmMachine.getVerificationRequest(otherUserId, transactionId)
return if (request != null) {
runBlocking { request.acceptWithMethods(methods) }
request.acceptWithMethods(methods)
if (request.isReady()) {
val qrcode = request.startQrVerification()
if (qrcode != null) {
this.dispatcher.dispatchTxAdded(qrcode)
dispatcher.dispatchTxAdded(qrcode)
}
true
@ -280,82 +279,50 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
}
}
override fun readyPendingVerificationInDMs(
methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
transactionId: String
): Boolean {
return readyPendingVerification(methods, otherUserId, transactionId)
}
override fun beginKeyVerification(
override suspend fun beginKeyVerification(
method: VerificationMethod,
otherUserId: String,
otherDeviceId: String,
transactionId: String?
transactionId: String
): String? {
return if (method == VerificationMethod.SAS) {
if (transactionId != null) {
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
val request = olmMachine.getVerificationRequest(otherUserId, transactionId)
runBlocking {
val sas = request?.startSasVerification()
val sas = request?.startSasVerification()
if (sas != null) {
dispatcher.dispatchTxAdded(sas)
sas.transactionId
} else {
null
}
}
if (sas != null) {
dispatcher.dispatchTxAdded(sas)
sas.transactionId
} else {
// This starts the short SAS flow, the one that doesn't start with
// a `m.key.verification.request`, Element web stopped doing this, might
// be wise do do so as well
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
// triggers this is called `manuallyVerify()`
runBlocking {
val verification = olmMachine.getDevice(otherUserId, otherDeviceId)?.startVerification()
if (verification != null) {
dispatcher.dispatchTxAdded(verification)
verification.transactionId
} else {
null
}
}
null
}
} else {
throw IllegalArgumentException("Unknown verification method")
}
}
override fun beginKeyVerificationInDMs(
method: VerificationMethod,
transactionId: String,
roomId: String,
otherUserId: String,
otherDeviceId: String
): String {
beginKeyVerification(method, otherUserId, otherDeviceId, transactionId)
// TODO what's the point of returning the same ID we got as an argument?
// We do this because the old verification service did so
return transactionId
}
override fun cancelVerificationRequest(request: PendingVerificationRequest) {
val verificationRequest = request.transactionId?.let {
this.olmMachine.getVerificationRequest(request.otherUserId, it)
override suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String? {
// This starts the short SAS flow, the one that doesn't start with
// a `m.key.verification.request`, Element web stopped doing this, might
// be wise do do so as well
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
// triggers this is called `manuallyVerify()`
val otherDevice = olmMachine.getDevice(otherUserId, otherDeviceId)
val verification = otherDevice?.startVerification()
return if (verification != null) {
dispatcher.dispatchTxAdded(verification)
verification.transactionId
} else {
null
}
runBlocking { verificationRequest?.cancel() }
}
override fun declineVerificationRequestInDMs(
otherUserId: String,
transactionId: String,
roomId: String
) {
val verificationRequest = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
runBlocking { verificationRequest?.cancel() }
override suspend fun cancelVerificationRequest(request: PendingVerificationRequest) {
request.transactionId ?: return
cancelVerificationRequest(request.otherUserId, request.transactionId)
}
override suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String) {
val verificationRequest = olmMachine.getVerificationRequest(otherUserId, transactionId)
verificationRequest?.cancel()
}
}

View file

@ -1,29 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.crypto.verification.qrcode
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import java.security.SecureRandom
fun generateSharedSecretV2(): String {
val secureRandom = SecureRandom()
// 8 bytes long
val secretBytes = ByteArray(8)
secureRandom.nextBytes(secretBytes)
return secretBytes.toBase64NoPadding()
}

View file

@ -177,7 +177,6 @@ internal class DefaultSession @Inject constructor(
assert(!isOpen)
isOpen = true
globalErrorHandler.listener = this
cryptoService.get().ensureDevice()
uiHandler.post {
lifecycleObservers.forEach {
it.onSessionStarted(this)

View file

@ -49,7 +49,6 @@ import org.matrix.android.sdk.internal.session.room.state.SendStateTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.search.SearchTask
import org.matrix.android.sdk.internal.session.space.DefaultSpace
import org.matrix.android.sdk.internal.util.awaitCallback
import java.security.InvalidParameterException
internal class DefaultRoom(override val roomId: String,
@ -117,9 +116,7 @@ internal class DefaultRoom(override val roomId: String,
}
override suspend fun prepareToEncrypt() {
awaitCallback<Unit> {
cryptoService.prepareToEncrypt(roomId, it)
}
cryptoService.prepareToEncrypt(roomId)
}
override suspend fun enableEncryption(algorithm: String, force: Boolean) {

View file

@ -156,7 +156,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
* Invoke the event decryption mechanism for a specific event
*/
private fun decryptIfNeeded(event: Event, roomId: String) {
private suspend fun decryptIfNeeded(event: Event, roomId: String) {
try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")

View file

@ -43,6 +43,7 @@ internal class RealmSendingEventsDataSource(
private var roomEntity: RoomEntity? = null
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private val builtEvents = ArrayList<TimelineEvent>()
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })

View file

@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
@ -126,7 +127,9 @@ internal class TimelineEventDecryptor @Inject constructor(
return
}
try {
val result = cryptoService.decryptEvent(request.event, timelineId)
val result = runBlocking {
cryptoService.decryptEvent(request.event, timelineId)
}
Timber.v("Successfully decrypted event ${event.eventId}")
realm.executeTransaction {
val eventId = event.eventId ?: return@executeTransaction

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
import dagger.Lazy
import io.realm.Realm
import io.realm.kotlin.createObject
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@ -343,7 +344,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
return roomEntity
}
private suspend fun handleTimelineEvents(realm: Realm,
private fun handleTimelineEvents(realm: Realm,
roomId: String,
roomEntity: RoomEntity,
eventList: List<Event>,
@ -458,7 +459,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private fun decryptIfNeeded(event: Event, roomId: String) {
try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
val result = runBlocking {
cryptoService.decryptEvent(event.copy(roomId = roomId), "")
}
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,

View file

@ -134,10 +134,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
onView(withId(R.id.bottomSheetFragmentContainer))
.check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
val request = existingSession!!.cryptoService().verificationService().requestKeyVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
existingSession!!.myUserId,
listOf(uiSession.sessionParams.deviceId!!)
val request = existingSession!!.cryptoService().verificationService().requestSelfKeyVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
)
val transactionId = request.transactionId!!

View file

@ -64,12 +64,12 @@ fun Session.startSyncing(context: Context) {
/**
* Tell is the session has unsaved e2e keys in the backup
*/
fun Session.hasUnsavedKeys(): Boolean {
suspend fun Session.hasUnsavedKeys(): Boolean {
return cryptoService().inboundGroupSessionsCount(false) > 0 &&
cryptoService().keysBackupService().state != KeysBackupState.ReadyToBackUp
}
fun Session.cannotLogoutSafely(): Boolean {
suspend fun Session.cannotLogoutSafely(): Boolean {
// has some encrypted chat
return hasUnsavedKeys() ||
// has local cross signing keys

View file

@ -26,5 +26,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async<KeysBackupVersionTrust> = Uninitialized,
val keysBackupState: KeysBackupState? = null,
val keysBackupVersion: KeysVersionResult? = null,
val remainingKeysToBackup: Int = 0,
val deleteBackupRequest: Async<Unit> = Uninitialized) :
MavericksState

View file

@ -124,10 +124,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
style(ItemStyle.BIG_TEXT)
hasIndeterminateProcess(true)
val totalKeys = host.session.cryptoService().inboundGroupSessionsCount(false)
val backedUpKeys = host.session.cryptoService().inboundGroupSessionsCount(true)
val remainingKeysToBackup = totalKeys - backedUpKeys
val remainingKeysToBackup = data.remainingKeysToBackup
if (data.keysBackupVersionTrust()?.usable == false) {
description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence())

View file

@ -18,7 +18,6 @@ package im.vector.app.features.crypto.keysbackup.settings
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -32,7 +31,6 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import timber.log.Timber
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
session: Session
@ -46,6 +44,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
companion object : MavericksViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> by hiltMavericksViewModelFactory()
private val cryptoService = session.cryptoService()
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
init {
@ -75,34 +74,12 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
private fun getKeysBackupTrust() = withState { state ->
val versionResult = keysBackupService.keysBackupVersion
Timber.d("BACKUP: HEEEEEEE $versionResult ${state.keysBackupVersionTrust}")
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
setState {
copy(
keysBackupVersionTrust = Loading(),
deleteBackupRequest = Uninitialized
)
}
Timber.d("BACKUP: HEEEEEEE TWO")
viewModelScope.launch {
try {
val data = keysBackupService.getKeysBackupTrust(versionResult)
Timber.d("BACKUP: HEEEE suceeeded $data")
setState {
copy(
keysBackupVersionTrust = Success(data)
)
}
} catch (failure: Throwable) {
Timber.d("BACKUP: HEEEE FAILED $failure")
setState {
copy(
keysBackupVersionTrust = Fail(failure)
)
}
}
setState { copy(deleteBackupRequest = Uninitialized) }
suspend {
keysBackupService.getKeysBackupTrust(versionResult)
}.execute {
copy(keysBackupVersionTrust = it)
}
}
}
@ -119,10 +96,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
keysBackupVersion = keysBackupService.keysBackupVersion
)
}
when (newState) {
KeysBackupState.BackingUp, KeysBackupState.WillBackUp -> updateKeysCount()
else -> Unit
}
getKeysBackupTrust()
}
private fun updateKeysCount() {
viewModelScope.launch {
val totalKeys = cryptoService.inboundGroupSessionsCount(false)
val backedUpKeys = cryptoService.inboundGroupSessionsCount(true)
val remainingKeysToBackup = totalKeys - backedUpKeys
setState {
copy(remainingKeysToBackup = remainingKeysToBackup)
}
}
}
private fun deleteCurrentBackup() {
val keysBackupService = keysBackupService

View file

@ -128,7 +128,7 @@ class KeyRequestHandler @Inject constructor(
}
if (deviceInfo.isUnknown) {
session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false), userId, deviceId)
session?.cryptoService()?.verificationService()?.markedLocallyAsManuallyVerified(userId, deviceId)
deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)

View file

@ -149,10 +149,12 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
// as we are going to reset, we'd better cancel all outgoing requests
// if not they could be accepted in the middle of the reset process
// and cause strange use cases
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
session.cryptoService().verificationService().cancelVerificationRequest(it)
viewModelScope.launch {
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
session.cryptoService().verificationService().cancelVerificationRequest(it)
}
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
}
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
}
private fun handleResetAll() {

View file

@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@ -91,12 +90,7 @@ class BootstrapCrossSigningTask @Inject constructor(
)
try {
awaitCallback<Unit> {
crossSigningService.initializeCrossSigning(
params.userInteractiveAuthInterceptor,
it
)
}
crossSigningService.initializeCrossSigning(params.userInteractiveAuthInterceptor)
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
return BootstrapResult.SuccessCrossSigningOnly
}

View file

@ -24,6 +24,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -42,7 +44,8 @@ import javax.inject.Singleton
class IncomingVerificationRequestHandler @Inject constructor(
private val context: Context,
private var avatarRenderer: Provider<AvatarRenderer>,
private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
private val popupAlertManager: PopupAlertManager,
private val coroutineScope: CoroutineScope) : VerificationService.Listener {
private var session: Session? = null
@ -61,7 +64,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
// TODO maybe check also if
val uid = "kvr_${tx.transactionId}"
when (tx.state) {
is VerificationTxState.OnStarted -> {
is VerificationTxState.OnStarted -> {
// Add a notification for every incoming request
val user = session?.getUser(tx.otherUserId)
val name = user?.toMatrixItem()?.getBestName() ?: tx.otherUserId
@ -88,12 +91,14 @@ class IncomingVerificationRequestHandler @Inject constructor(
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
}
}
dismissedAction = Runnable {
dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
tx.cancel()
}
addButton(
context.getString(R.string.action_ignore),
{ tx.cancel() }
LaunchCoroutineRunnable(coroutineScope) {
tx.cancel()
}
)
addButton(
context.getString(R.string.action_open),
@ -160,10 +165,9 @@ class IncomingVerificationRequestHandler @Inject constructor(
}
}
}
dismissedAction = Runnable {
session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
pr.transactionId ?: "",
pr.roomId ?: ""
dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
session?.cryptoService()?.verificationService()?.cancelVerificationRequest(pr.otherUserId,
pr.transactionId ?: ""
)
}
colorAttribute = R.attr.vctr_notice_secondary
@ -181,6 +185,14 @@ class IncomingVerificationRequestHandler @Inject constructor(
}
}
private class LaunchCoroutineRunnable(private val coroutineScope: CoroutineScope, private val block: suspend () -> Unit) : Runnable {
override fun run() {
coroutineScope.launch {
block()
}
}
}
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
"verificationRequest_${pr.transactionId}"
}

View file

@ -16,8 +16,6 @@
package im.vector.app.features.crypto.verification
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
@ -131,34 +129,37 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction
}
val hasAnyOtherSession = session.cryptoService()
.getCryptoDeviceInfo(session.myUserId)
.any {
it.deviceId != session.sessionParams.deviceId
}
viewModelScope.launch {
setState {
copy(
otherUserMxItem = userItem?.toMatrixItem(),
sasTransactionState = sasTx?.state,
qrTransactionState = qrTx?.state,
transactionId = pr?.transactionId ?: initialState.verificationId,
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
isMe = initialState.otherUserId == session.myUserId,
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
hasAnyOtherSession = hasAnyOtherSession
)
}
val hasAnyOtherSession = session.cryptoService()
.getCryptoDeviceInfoList(session.myUserId)
.any {
it.deviceId != session.sessionParams.deviceId
}
if (autoReady) {
// TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService()
.readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
pr!!.otherUserId,
pr.transactionId ?: ""
)
setState {
copy(
otherUserMxItem = userItem?.toMatrixItem(),
sasTransactionState = sasTx?.state,
qrTransactionState = qrTx?.state,
transactionId = pr?.transactionId ?: initialState.verificationId,
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
isMe = initialState.otherUserId == session.myUserId,
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
hasAnyOtherSession = hasAnyOtherSession
)
}
if (autoReady) {
// TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService()
.readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
pr!!.otherUserId,
pr.transactionId ?: ""
)
}
}
}
@ -192,14 +193,16 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
session.cryptoService()
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
session.cryptoService().verificationService().cancelVerificationRequest(it)
}
session.cryptoService()
.verificationService()
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
?.cancel(CancelCode.User)
viewModelScope.launch {
session.cryptoService()
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
session.cryptoService().verificationService().cancelVerificationRequest(it)
}
session.cryptoService()
.verificationService()
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
?.cancel(CancelCode.User)
}
}
fun continueFromCancel() {
@ -232,109 +235,25 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
when (action) {
is VerificationAction.RequestVerificationByDM -> {
if (roomId == null) {
val localId = LocalEcho.createLocalEchoId()
setState {
copy(
pendingLocalId = localId,
pendingRequest = Loading()
)
}
viewModelScope.launch {
val result = runCatching { session.createDirectRoom(otherUserId) }
result.fold(
{ data ->
setState {
copy(
roomId = data,
pendingRequest = Success(
session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
otherUserId,
data,
pendingLocalId
)
)
)
}
},
{ failure ->
setState {
copy(pendingRequest = Fail(failure))
}
}
)
}
} else {
setState {
copy(
pendingRequest = Success(session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
)
)
}
}
Unit
handleRequestVerificationByDM(roomId, otherUserId)
}
is VerificationAction.StartSASVerification -> {
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
?: return@withState
val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
if (roomId == null) {
session.cryptoService().verificationService().beginKeyVerification(
VerificationMethod.SAS,
otherUserId = request.otherUserId,
otherDeviceId = otherDevice ?: "",
transactionId = action.pendingRequestTransactionId
)
} else {
session.cryptoService().verificationService().beginKeyVerificationInDMs(
VerificationMethod.SAS,
transactionId = action.pendingRequestTransactionId,
roomId = roomId,
otherUserId = request.otherUserId,
otherDeviceId = otherDevice ?: ""
)
}
Unit
handleStartSASVerification(otherUserId, action)
}
is VerificationAction.RemoteQrCodeScanned -> {
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.userHasScannedOtherQrCode(action.scannedData)
handleRemoteQrCodeScanned(action)
}
is VerificationAction.OtherUserScannedSuccessfully -> {
val transactionId = state.transactionId ?: return@withState
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.otherUserScannedMyQrCode()
handleOtherUserScannedSuccessfully(state.transactionId, otherUserId)
}
is VerificationAction.OtherUserDidNotScanned -> {
val transactionId = state.transactionId ?: return@withState
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.otherUserDidNotScannedMyQrCode()
handleOtherUserDidNotScanned(state.transactionId, otherUserId)
}
is VerificationAction.SASMatchAction -> {
(session.cryptoService().verificationService()
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
handleSASMatchAction(action)
}
is VerificationAction.SASDoNotMatchAction -> {
(session.cryptoService().verificationService()
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
as? SasVerificationTransaction)
?.shortCodeDoesNotMatch()
handleSASDoNotMatchAction(action)
}
is VerificationAction.GotItConclusion -> {
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
@ -365,6 +284,85 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}.exhaustive
}
private fun handleStartSASVerification(otherUserId: String, action: VerificationAction.StartSASVerification) {
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
?: return
viewModelScope.launch {
session.cryptoService().verificationService().beginKeyVerification(
VerificationMethod.SAS,
otherUserId = request.otherUserId,
transactionId = action.pendingRequestTransactionId
)
}
}
private fun handleSASDoNotMatchAction(action: VerificationAction.SASDoNotMatchAction) {
viewModelScope.launch {
(session.cryptoService().verificationService()
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
as? SasVerificationTransaction)
?.shortCodeDoesNotMatch()
}
}
private fun handleSASMatchAction(action: VerificationAction.SASMatchAction) {
viewModelScope.launch {
(session.cryptoService().verificationService()
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
}
}
private fun handleOtherUserDidNotScanned(transactionId: String?, otherUserId: String) {
transactionId ?: return
viewModelScope.launch {
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.otherUserDidNotScannedMyQrCode()
}
}
private fun handleOtherUserScannedSuccessfully(transactionId: String?, otherUserId: String) {
transactionId ?: return
viewModelScope.launch {
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.otherUserScannedMyQrCode()
}
}
private fun handleRemoteQrCodeScanned(action: VerificationAction.RemoteQrCodeScanned) {
viewModelScope.launch {
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.userHasScannedOtherQrCode(action.scannedData)
}
}
private fun handleRequestVerificationByDM(roomId: String?, otherUserId: String) {
viewModelScope.launch {
val localId = LocalEcho.createLocalEchoId()
val dmRoomId = roomId ?: session.createDirectRoom(otherUserId)
setState { copy(pendingLocalId = localId, roomId = dmRoomId) }
suspend {
session
.cryptoService()
.verificationService()
.requestKeyVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
otherUserId,
dmRoomId,
localId
)
}.execute {
copy(pendingRequest = it, roomId = dmRoomId)
}
}
}
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
viewModelScope.launch(Dispatchers.IO) {
try {
@ -446,60 +444,66 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
}
private fun handleTransactionUpdate(state: VerificationBottomSheetViewState, tx: VerificationTransaction) {
viewModelScope.launch {
if (state.selfVerificationMode && state.transactionId == null) {
// is this an incoming with that user
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
// Also auto accept incoming if needed!
// TODO is state.transactionId ever null for self verifications, doesn't seem
// like this will ever trigger
if (tx is SasVerificationTransaction && tx.state == VerificationTxState.OnStarted) {
tx.acceptVerification()
}
/*
if (tx is IncomingSasVerificationTransaction) {
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
tx.performAccept()
}
}
*/
// Use this one!
setState {
copy(
transactionId = tx.transactionId,
sasTransactionState = tx.state.takeIf { tx is SasVerificationTransaction },
qrTransactionState = tx.state.takeIf { tx is QrCodeVerificationTransaction }
)
}
}
}
when (tx) {
is SasVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request
setState {
copy(
sasTransactionState = tx.state
)
}
}
}
is QrCodeVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A QR tx has been started following this request
setState {
copy(
qrTransactionState = tx.state
)
}
}
}
}
}
}
override fun transactionCreated(tx: VerificationTransaction) {
transactionUpdated(tx)
}
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
if (state.selfVerificationMode && state.transactionId == null) {
// is this an incoming with that user
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
// Also auto accept incoming if needed!
// TODO is state.transactionId ever null for self verifications, doesn't seem
// like this will ever trigger
if (tx is SasVerificationTransaction && tx.state == VerificationTxState.OnStarted) {
tx.acceptVerification()
}
/*
if (tx is IncomingSasVerificationTransaction) {
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
tx.performAccept()
}
}
*/
// Use this one!
setState {
copy(
transactionId = tx.transactionId,
sasTransactionState = tx.state.takeIf { tx is SasVerificationTransaction },
qrTransactionState = tx.state.takeIf { tx is QrCodeVerificationTransaction }
)
}
}
}
when (tx) {
is SasVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request
setState {
copy(
sasTransactionState = tx.state
)
}
}
}
is QrCodeVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A QR tx has been started following this request
setState {
copy(
qrTransactionState = tx.state
)
}
}
}
}
handleTransactionUpdate(state, tx)
}
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
@ -514,12 +518,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
if (!pr.isReady) {
// auto ready in this case, as we are waiting
// TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService()
.readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
pr.otherUserId,
pr.transactionId ?: ""
)
viewModelScope.launch {
session.cryptoService().verificationService()
.readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
pr.otherUserId,
pr.transactionId ?: ""
)
}
}
// Use this one!

View file

@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -220,30 +219,27 @@ class HomeActivityViewModel @AssistedInject constructor(
// Try to initialize cross signing in background if possible
Timber.d("Initialize cross signing...")
try {
awaitCallback<Unit> {
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
// We missed server grace period or it's not setup, see if we remember locally password
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
errCode == null &&
reAuthHelper.data != null) {
promise.resume(
UserPasswordAuth(
session = flowResponse.session,
user = session.myUserId,
password = reAuthHelper.data
)
)
} else {
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
}
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
// We missed server grace period or it's not setup, see if we remember locally password
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
errCode == null &&
reAuthHelper.data != null) {
promise.resume(
UserPasswordAuth(
session = flowResponse.session,
user = session.myUserId,
password = reAuthHelper.data
)
)
} else {
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
}
},
callback = it
)
Timber.d("Initialize cross signing SUCCESS")
}
}
},
)
Timber.d("Initialize cross signing SUCCESS")
} catch (failure: Throwable) {
Timber.e(failure, "Failed to initialize cross signing")
}

View file

@ -80,6 +80,7 @@ class HomeDrawerFragment @Inject constructor(
}
// Sign out
views.homeDrawerHeaderSignoutView.debouncedClicks {
signout()
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
SignOutUiWorker(requireActivity()).perform()
}
@ -118,4 +119,7 @@ class HomeDrawerFragment @Inject constructor(
navigator.openDebug(requireActivity())
}
}
private fun signout() {
}
}

View file

@ -32,10 +32,11 @@ import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import org.matrix.android.sdk.api.NoOpMatrixCallback
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.MatrixItem
@ -58,7 +59,7 @@ data class DeviceDetectionInfo(
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
session: Session,
private val vectorPreferences: VectorPreferences) :
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
sealed class Action : VectorViewModelAction {
data class IgnoreDevice(val deviceIds: List<String>) : Action()
@ -75,12 +76,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
init {
val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
.firstOrNull { it.deviceId == session.sessionParams.deviceId }
?.firstTimeSeenLocalTs
?: System.currentTimeMillis()
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
ignoredDeviceList.addAll(
vectorPreferences.getUnknownDeviceDismissedList().also {
Timber.v("## Detector - Remembered ignored list $it")
@ -90,10 +85,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
combine(
session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningPrivateKeys()
) { cryptoList, infoList, pInfo ->
session.flow().liveCrossSigningPrivateKeys(),
session.firstTimeDeviceSeen(),
) { cryptoList, infoList, pInfo, firstTimeDeviceSeen ->
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
Timber.v("## Detector - Current Session first time seen $firstTimeDeviceSeen")
infoList
.filter { info ->
// filter verified session, by checking the crypto device info
@ -106,7 +103,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
DeviceDetectionInfo(
deviceInfo,
deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive,
deviceKnownSince > firstTimeDeviceSeen + 60_000, // short window to avoid false positive,
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
)
}
@ -125,12 +122,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
.sample(5_000)
.onEach {
// If we have a new crypto device change, we might want to trigger refresh of device info
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().fetchDevicesList()
}
.launchIn(viewModelScope)
// trigger a refresh of lastSeen / last Ip
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
viewModelScope.launch {
session.cryptoService().fetchDevicesList()
}
}
override fun handle(action: Action) {
@ -154,4 +153,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
super.onCleared()
}
private fun Session.firstTimeDeviceSeen() = flow {
val value = cryptoService().getCryptoDeviceInfoList(myUserId)
.firstOrNull { it.deviceId == sessionParams.deviceId }
?.firstTimeSeenLocalTs
?: System.currentTimeMillis()
emit(value)
}
}

View file

@ -994,22 +994,24 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
action.otherUserId,
room.roomId,
action.transactionId)) {
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
} else {
// TODO
viewModelScope.launch {
if (session.cryptoService().verificationService().readyPendingVerification(
supportedVerificationMethodsProvider.provide(),
action.otherUserId,
action.transactionId)) {
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
} else {
// TODO
}
}
}
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
session.cryptoService().verificationService().declineVerificationRequestInDMs(
action.otherUserId,
action.transactionId,
room.roomId)
viewModelScope.launch {
session.cryptoService().verificationService().cancelVerificationRequest(
action.otherUserId,
action.transactionId)
}
}
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {

View file

@ -164,11 +164,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
val nonNullTimelineEvent = timelineEvent() ?: return@onEach
eventIdFlow.tryEmit(nonNullTimelineEvent.eventId)
val events = actionsForEvent(nonNullTimelineEvent, permissions)
setState {
copy(
eventId = nonNullTimelineEvent.eventId,
messageBody = computeMessageBody(nonNullTimelineEvent),
actions = actionsForEvent(nonNullTimelineEvent, permissions)
actions = events
)
}
}
@ -246,7 +247,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
}
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
private suspend fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
val messageContent = timelineEvent.getLastMessageContent()
val msgType = messageContent?.msgType
@ -317,7 +318,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
// TODO sent by me or sufficient power level
}
private fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
private suspend fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
actionPermissions: ActionPermissions,
messageContent: MessageContent?,
msgType: String?) {
@ -400,7 +401,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
) {
add(EventSharedAction.UseKeyBackup)
}
if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1 ||
if (session.cryptoService().getCryptoDeviceInfoList(session.myUserId).size > 1 ||
timelineEvent.senderInfo.userId != session.myUserId) {
add(EventSharedAction.ReRequestKey(timelineEvent.eventId))
}

View file

@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.crypto.VerificationState
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
@ -138,53 +139,55 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
}
}
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration {
return if (
event.root.sendState == SendState.SYNCED &&
roomSummary?.isEncrypted.orFalse() &&
// is user verified
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true) {
val ts = roomSummary?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0
if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
E2EDecoration.NONE
} else {
val sendingDevice = event.root.content
.toModel<EncryptedEventContent>()
?.deviceId
?.let { deviceId ->
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
}
when {
sendingDevice == null -> {
// For now do not decorate this with warning
// maybe it's a deleted session
E2EDecoration.NONE
}
sendingDevice.trustLevel == null -> {
E2EDecoration.WARN_SENT_BY_UNKNOWN
}
sendingDevice.trustLevel?.isVerified().orFalse() -> {
E2EDecoration.NONE
}
else -> {
E2EDecoration.WARN_SENT_BY_UNVERIFIED
}
}
}
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration = runBlocking {
if (event.root.sendState != SendState.SYNCED) {
return@runBlocking E2EDecoration.NONE
}
if (!roomSummary?.isEncrypted.orFalse()) {
return@runBlocking E2EDecoration.NONE
}
val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
if (!isUserVerified) {
return@runBlocking E2EDecoration.NONE
}
val ts = roomSummary?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0
return@runBlocking if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
E2EDecoration.NONE
} else {
if (event.root.isStateEvent()) {
// Do not warn for state event, they are always in clear
E2EDecoration.NONE
} else {
// If event is in clear after the room enabled encryption we should warn
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
val sendingDevice = event.root.content
.toModel<EncryptedEventContent>()
?.deviceId
?.let { deviceId ->
session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId)
}
when {
sendingDevice == null -> {
// For now do not decorate this with warning
// maybe it's a deleted session
E2EDecoration.NONE
}
sendingDevice.trustLevel == null -> {
E2EDecoration.WARN_SENT_BY_UNKNOWN
}
sendingDevice.trustLevel?.isVerified().orFalse() -> {
E2EDecoration.NONE
}
else -> {
E2EDecoration.WARN_SENT_BY_UNVERIFIED
}
}
}
} else {
E2EDecoration.NONE
if (event.root.isStateEvent()) {
// Do not warn for state event, they are always in clear
E2EDecoration.NONE
} else {
// If event is in clear after the room enabled encryption we should warn
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
}
}
}

View file

@ -100,6 +100,8 @@ import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder
import im.vector.app.space
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
@ -116,7 +118,8 @@ class DefaultNavigator @Inject constructor(
private val widgetArgsBuilder: WidgetArgsBuilder,
private val appStateHandler: AppStateHandler,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val features: VectorFeatures
private val features: VectorFeatures,
private val coroutineScope: CoroutineScope
) : Navigator {
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
@ -193,55 +196,57 @@ class DefaultNavigator @Inject constructor(
}
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
val session = sessionHolder.getSafeActiveSession() ?: return
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
?: return
if (tx is SasVerificationTransaction && tx.isIncoming) {
tx.acceptVerification()
}
coroutineScope.launch {
val session = sessionHolder.getSafeActiveSession() ?: return@launch
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
?: return@launch
if (tx is SasVerificationTransaction && tx.isIncoming) {
tx.acceptVerification()
}
if (context is AppCompatActivity) {
VerificationBottomSheet.withArgs(
roomId = null,
otherUserId = otherUserId,
transactionId = sasTransactionId
).show(context.supportFragmentManager, "REQPOP")
if (context is AppCompatActivity) {
VerificationBottomSheet.withArgs(
roomId = null,
otherUserId = otherUserId,
transactionId = sasTransactionId
).show(context.supportFragmentManager, "REQPOP")
}
}
}
override fun requestSessionVerification(context: Context, otherSessionId: String) {
val session = sessionHolder.getSafeActiveSession() ?: return
val pr = session.cryptoService().verificationService().requestKeyVerification(
supportedVerificationMethodsProvider.provide(),
session.myUserId,
listOf(otherSessionId)
)
if (context is AppCompatActivity) {
VerificationBottomSheet.withArgs(
roomId = null,
otherUserId = session.myUserId,
transactionId = pr.transactionId
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
coroutineScope.launch {
val session = sessionHolder.getSafeActiveSession() ?: return@launch
val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
supportedVerificationMethodsProvider.provide()
)
if (context is AppCompatActivity) {
VerificationBottomSheet.withArgs(
roomId = null,
otherUserId = session.myUserId,
transactionId = pr.transactionId
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
}
}
}
override fun requestSelfSessionVerification(context: Context) {
val session = sessionHolder.getSafeActiveSession() ?: return
val otherSessions = session.cryptoService()
.getCryptoDeviceInfo(session.myUserId)
.filter { it.deviceId != session.sessionParams.deviceId }
.map { it.deviceId }
if (context is AppCompatActivity) {
if (otherSessions.isNotEmpty()) {
val pr = session.cryptoService().verificationService().requestKeyVerification(
supportedVerificationMethodsProvider.provide(),
session.myUserId,
otherSessions)
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
} else {
VerificationBottomSheet.forSelfVerification(session)
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
coroutineScope.launch {
val session = sessionHolder.getSafeActiveSession() ?: return@launch
val otherSessions = session.cryptoService()
.getCryptoDeviceInfoList(session.myUserId)
.filter { it.deviceId != session.sessionParams.deviceId }
.map { it.deviceId }
if (context is AppCompatActivity) {
if (otherSessions.isNotEmpty()) {
val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
supportedVerificationMethodsProvider.provide())
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
} else {
VerificationBottomSheet.forSelfVerification(session)
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
}
}
}
}
@ -404,13 +409,15 @@ class DefaultNavigator @Inject constructor(
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {
// if cross signing is enabled and trusted or not set up at all we should propose full 4S
sessionHolder.getSafeActiveSession()?.let { session ->
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
session.cryptoService().crossSigningService().canCrossSign()) {
(context as? AppCompatActivity)?.let {
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
coroutineScope.launch {
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
session.cryptoService().crossSigningService().canCrossSign()) {
(context as? AppCompatActivity)?.let {
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
}
} else {
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
}
} else {
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
}
}
}

View file

@ -190,7 +190,7 @@ class NotifiableEventResolver @Inject constructor(
}
}
private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
private suspend fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
if (root.isEncrypted() && root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
// for now decrypt sync

View file

@ -30,9 +30,9 @@ import im.vector.app.core.di.SingletonEntryPoint
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow
@ -124,8 +124,13 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
if (!initialState.allowDeviceAction) return
session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, initialState.userId, action.deviceId, null)?.let { txID ->
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
viewModelScope.launch {
session.cryptoService().verificationService().beginDeviceVerification(
otherUserId = initialState.userId,
otherDeviceId = action.deviceId,
)?.let { txID ->
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
}
}
}
}

View file

@ -16,7 +16,6 @@
package im.vector.app.features.roomprofile.members
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -94,8 +93,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
if (room.isEncrypted()) {
room.flow().liveRoomMembers(roomMemberQueryParams)
.flatMapLatest { membersSummary ->
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
.asFlow()
session.cryptoService().getLiveCryptoDeviceInfoList(membersSummary.map { it.userId })
.catch { Timber.e(it) }
.map { deviceList ->
// If any key change, emit the userIds list

View file

@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
@ -317,31 +315,32 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// Todo this should be refactored and use same state as 4S section
private fun refreshXSigningStatus() {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
lifecycleScope.launchWhenResumed {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
when {
xSigningKeyCanSign -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
}
xSigningKeysAreTrusted -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
}
xSigningIsEnableInAccount -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
}
else -> {
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
when {
xSigningKeyCanSign -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
}
xSigningKeysAreTrusted -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
}
xSigningIsEnableInAccount -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
}
else -> {
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
}
}
mCrossSigningStatePreference.isVisible = true
}
mCrossSigningStatePreference.isVisible = true
}
private val saveMegolmStartForActivityResult = registerStartForActivityResult {
@ -523,7 +522,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
/**
* Build the cryptography preference section.
*/
private fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
private suspend fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
showDeviceListPref.isEnabled = devices.isNotEmpty()
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
@ -553,7 +552,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
// crypto section: device key (fingerprint)
val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId)
val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId)
val fingerprint = deviceInfo?.fingerprint()
if (fingerprint?.isNotEmpty() == true) {
@ -579,28 +578,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// ==============================================================================================================
private fun refreshMyDevice() {
session.cryptoService().getUserDevices(session.myUserId).map {
DeviceInfo(
userId = session.myUserId,
deviceId = it.deviceId,
displayName = it.displayName()
)
}.let {
refreshCryptographyPreference(it)
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
session.cryptoService().getUserDevices(session.myUserId).map {
DeviceInfo(
userId = session.myUserId,
deviceId = it.deviceId,
displayName = it.displayName()
)
}.let {
refreshCryptographyPreference(it)
}
// TODO Move to a ViewModel...
val devicesList = session.cryptoService().fetchDevicesList()
refreshCryptographyPreference(devicesList)
}
// TODO Move to a ViewModel...
session.cryptoService().fetchDevicesList(object : MatrixCallback<DevicesListResponse> {
override fun onSuccess(data: DevicesListResponse) {
if (isAdded) {
refreshCryptographyPreference(data.devices.orEmpty())
}
}
override fun onFailure(failure: Throwable) {
if (isAdded) {
refreshCryptographyPreference(emptyList())
}
}
})
}
}

View file

@ -29,6 +29,7 @@ import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.login.ReAuthHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -41,7 +42,6 @@ import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -55,25 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
init {
combine(
session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningInfo(session.myUserId)
) { myDevicesInfo, mxCrossSigningInfo ->
myDevicesInfo to mxCrossSigningInfo
}
.execute { data ->
val crossSigningKeys = data.invoke()?.second?.getOrNull()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
copy(
crossSigningInfo = crossSigningKeys,
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
xSigningKeyCanSign = xSigningKeyCanSign
)
}
observeCrossSigning()
}
var uiaContinuation: Continuation<UIABaseAuth>? = null
@ -90,29 +72,27 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
viewModelScope.launch(Dispatchers.IO) {
try {
awaitCallback<Unit> {
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse,
errCode: String?,
promise: Continuation<UIABaseAuth>) {
Timber.d("## UIA : initializeCrossSigning UIA")
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
reAuthHelper.data != null && errCode == null) {
UserPasswordAuth(
session = null,
user = session.myUserId,
password = reAuthHelper.data
).let { promise.resume(it) }
} else {
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse,
errCode: String?,
promise: Continuation<UIABaseAuth>) {
Timber.d("## UIA : initializeCrossSigning UIA")
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
reAuthHelper.data != null && errCode == null) {
UserPasswordAuth(
session = null,
user = session.myUserId,
password = reAuthHelper.data
).let { promise.resume(it) }
} else {
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
}, it)
}
}
})
} catch (failure: Throwable) {
handleInitializeXSigningError(failure)
} finally {
@ -149,6 +129,28 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
}.exhaustive
}
private fun observeCrossSigning() {
combine(
session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveCrossSigningInfo(session.myUserId)
) { myDevicesInfo, mxCrossSigningInfo ->
myDevicesInfo to mxCrossSigningInfo
}.onEach { data ->
val crossSigningKeys = data.second.getOrNull()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
setState {
copy(
crossSigningInfo = crossSigningKeys,
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
xSigningKeyCanSign = xSigningKeyCanSign
)
}
}
}
private fun handleInitializeXSigningError(failure: Throwable) {
Timber.e(failure, "## CrossSigning - Failed to initialize cross signing")
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing))))

View file

@ -15,7 +15,6 @@
*/
package im.vector.app.features.settings.devices
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -26,6 +25,7 @@ import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
@ -43,14 +43,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
by hiltMavericksViewModelFactory()
init {
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
)
}
initState()
session.flow().liveCrossSigningInfo(session.myUserId)
.execute {
copy(
@ -78,10 +71,6 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
)
}
setState {
copy(deviceInfo = Loading())
}
session.flow().liveMyDevicesInfo()
.map { devices ->
devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId)
@ -91,6 +80,21 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
}
}
private fun initState() {
viewModelScope.launch {
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
val isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
setState {
copy(
hasAccountCrossSigning = hasAccountCrossSigning,
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
isRecoverySetup = isRecoverySetup
)
}
}
}
override fun handle(action: EmptyAction) {
}
}

View file

@ -35,7 +35,6 @@ import im.vector.app.core.utils.PublishDataSource
import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.login.ReAuthHelper
import im.vector.lib.core.utils.flow.throttleFirst
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
@ -43,8 +42,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
@ -53,17 +50,14 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation
@ -106,15 +100,7 @@ class DevicesViewModel @AssistedInject constructor(
private val refreshSource = PublishDataSource<Unit>()
init {
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
myDeviceId = session.sessionParams.deviceId ?: ""
)
}
initState()
combine(
session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo()
@ -155,7 +141,7 @@ class DevicesViewModel @AssistedInject constructor(
.sample(5_000)
.onEach {
// If we have a new crypto device change, we might want to trigger refresh of device info
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().fetchDevicesList()
}
.launchIn(viewModelScope)
@ -168,7 +154,7 @@ class DevicesViewModel @AssistedInject constructor(
refreshSource.stream().throttleFirst(4_000)
.onEach {
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().fetchDevicesList()
session.cryptoService().downloadKeys(listOf(session.myUserId), true)
}
.launchIn(viewModelScope)
@ -176,6 +162,21 @@ class DevicesViewModel @AssistedInject constructor(
queryRefreshDevicesList()
}
private fun initState() {
viewModelScope.launch {
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
val myDeviceId = session.sessionParams.deviceId ?: ""
setState {
copy(
hasAccountCrossSigning = hasAccountCrossSigning,
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
myDeviceId = myDeviceId
)
}
}
}
override fun onCleared() {
session.cryptoService().verificationService().removeListener(this)
super.onCleared()
@ -240,13 +241,15 @@ class DevicesViewModel @AssistedInject constructor(
}
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
val txID = session.cryptoService()
.verificationService()
.beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null)
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
session.myUserId,
txID
))
viewModelScope.launch {
val txID = session.cryptoService()
.verificationService()
.beginDeviceVerification(session.myUserId, action.deviceId)
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
session.myUserId,
txID
))
}
}
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
@ -268,8 +271,7 @@ class DevicesViewModel @AssistedInject constructor(
}
} else {
// legacy
session.cryptoService().setDeviceVerification(
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
session.cryptoService().verificationService().markedLocallyAsManuallyVerified(
action.cryptoDeviceInfo.userId,
action.cryptoDeviceInfo.deviceId)
}
@ -288,27 +290,21 @@ class DevicesViewModel @AssistedInject constructor(
}
private fun handleRename(action: DevicesAction.Rename) {
session.cryptoService().setDeviceName(action.deviceId, action.newName, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
viewModelScope.launch {
try {
session.cryptoService().setDeviceName(action.deviceId, action.newName)
setState {
copy(
request = Success(data)
)
copy(request = Success(Unit))
}
// force settings update
queryRefreshDevicesList()
}
override fun onFailure(failure: Throwable) {
} catch (failure: Throwable) {
setState {
copy(
request = Fail(failure)
)
copy(request = Fail(failure))
}
_viewEvents.post(DevicesViewEvents.Failure(failure))
}
})
}
}
/**
@ -323,39 +319,32 @@ class DevicesViewModel @AssistedInject constructor(
)
}
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch {
try {
awaitCallback<Unit> {
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
Timber.d("## UIA : deleteDevice UIA")
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
UserPasswordAuth(
session = null,
user = session.myUserId,
password = reAuthHelper.data
).let { promise.resume(it) }
} else {
Timber.d("## UIA : deleteDevice UIA > start reauth activity")
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
Timber.d("## UIA : deleteDevice UIA")
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
UserPasswordAuth(
session = null,
user = session.myUserId,
password = reAuthHelper.data
).let { promise.resume(it) }
} else {
Timber.d("## UIA : deleteDevice UIA > start reauth activity")
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
}, it)
}
}
})
setState {
copy(
request = Success(Unit)
)
copy(request = Success(Unit))
}
// force settings update
queryRefreshDevicesList()
} catch (failure: Throwable) {
setState {
copy(
request = Fail(failure)
)
copy(request = Fail(failure))
}
if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
_viewEvents.post(DevicesViewEvents.Failure(Exception(stringProvider.getString(R.string.authentication_error))))

View file

@ -95,7 +95,7 @@ class SoftLogoutActivity : LoginActivity() {
MainActivity.restartApp(this, MainActivityArgs())
}
views.loginLoading.isVisible = softLogoutViewState.isLoading()
views.loginLoading.isVisible = softLogoutViewState.isLoading
}
companion object {

View file

@ -34,6 +34,7 @@ import im.vector.app.features.signout.soft.epoxy.loginRedButtonItem
import im.vector.app.features.signout.soft.epoxy.loginTextItem
import im.vector.app.features.signout.soft.epoxy.loginTitleItem
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject
class SoftLogoutController @Inject constructor(
@ -52,6 +53,7 @@ class SoftLogoutController @Inject constructor(
override fun buildModels() {
val safeViewState = viewState ?: return
if (safeViewState.hasUnsavedKeys is Incomplete) return
buildHeader(safeViewState)
buildForm(safeViewState)
@ -78,7 +80,7 @@ class SoftLogoutController @Inject constructor(
state.userDisplayName,
state.userId))
}
if (state.hasUnsavedKeys) {
if (state.hasUnsavedKeys().orFalse()) {
loginTextItem {
id("signText2")
text(host.stringProvider.getString(R.string.soft_logout_signin_e2e_warning_notice))

View file

@ -32,6 +32,7 @@ import im.vector.app.features.login.AbstractLoginFragment
import im.vector.app.features.login.LoginAction
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.LoginViewEvents
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject
/**
@ -118,7 +119,7 @@ class SoftLogoutFragment @Inject constructor(
withState(softLogoutViewModel) { state ->
cleanupUi()
val messageResId = if (state.hasUnsavedKeys) {
val messageResId = if (state.hasUnsavedKeys().orFalse()) {
R.string.soft_logout_clear_data_dialog_e2e_warning_content
} else {
R.string.soft_logout_clear_data_dialog_content

View file

@ -69,7 +69,6 @@ class SoftLogoutViewModel @AssistedInject constructor(
userId = userId,
deviceId = session.sessionParams.deviceId.orEmpty(),
userDisplayName = session.getUser(userId)?.displayName ?: userId,
hasUnsavedKeys = session.hasUnsavedKeys()
)
} else {
SoftLogoutViewState(
@ -77,17 +76,25 @@ class SoftLogoutViewModel @AssistedInject constructor(
userId = "",
deviceId = "",
userDisplayName = "",
hasUnsavedKeys = false
)
}
}
}
init {
checkHasUnsavedKeys()
// Get the supported login flow
getSupportedLoginFlow()
}
private fun checkHasUnsavedKeys() {
suspend {
session.hasUnsavedKeys()
}.execute {
copy(hasUnsavedKeys = it)
}
}
private fun getSupportedLoginFlow() {
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()

View file

@ -30,13 +30,12 @@ data class SoftLogoutViewState(
val userId: String,
val deviceId: String,
val userDisplayName: String,
val hasUnsavedKeys: Boolean,
val hasUnsavedKeys: Async<Boolean> = Uninitialized,
val enteredPassword: String = ""
) : MavericksState {
fun isLoading(): Boolean {
return asyncLoginAction is Loading ||
val isLoading: Boolean =
asyncLoginAction is Loading ||
// Keep loading when it is success because of the delay to switch to the next Activity
asyncLoginAction is Success
}
}

View file

@ -130,14 +130,14 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
/**
* Safe way to get the number of keys to backup
*/
fun getNumberOfKeysToBackup(): Int {
private suspend fun getNumberOfKeysToBackup(): Int {
return session.cryptoService().inboundGroupSessionsCount(false)
}
/**
* Safe way to tell if there are more keys on the server
*/
fun canRestoreKeys(): Boolean {
private suspend fun canRestoreKeys(): Boolean {
return session.cryptoService().keysBackupService().canRestoreKeys()
}
@ -161,5 +161,5 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
}
}
override fun handle(action: EmptyAction) {}
override fun handle(action: EmptyAction) = Unit
}

View file

@ -17,17 +17,25 @@
package im.vector.app.features.workers.signout
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.extensions.cannotLogoutSafely
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
class SignOutUiWorker(private val activity: FragmentActivity) {
fun perform() {
val session = activity.singletonEntryPoint().activeSessionHolder().getSafeActiveSession() ?: return
activity.lifecycleScope.perform(session)
}
private fun CoroutineScope.perform(session: Session) = launch {
if (session.cannotLogoutSafely()) {
// The backup check on logout flow has to be displayed if there are keys in the store, and the keys backup state is not Ready
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance()

View file

@ -30,9 +30,9 @@ class FakeCryptoService : CryptoService by mockk() {
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
override fun getLiveCryptoDeviceInfo(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
override fun getLiveCryptoDeviceInfoList(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
override fun getLiveCryptoDeviceInfoList(userIds: List<String>) = MutableLiveData(
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
)
}