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>> { fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> {
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() return session.cryptoService().getLiveCryptoDeviceInfoList(userId)
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.cryptoService().getCryptoDeviceInfo(userId) session.cryptoService().getCryptoDeviceInfoList(userId)
} }
} }
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> { fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId)
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional() session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
} }
} }
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> { fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys()
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional() session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
} }

View File

@ -110,6 +110,9 @@ dependencies {
implementation libs.jetbrains.coroutinesAndroid implementation libs.jetbrains.coroutinesAndroid
implementation 'org.matrix.rustcomponents:crypto-android:0.1.1-SNAPSHOT' 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 'net.java.dev.jna:jna:5.10.0@aar'
implementation libs.androidx.appCompat 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)) 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) { while (true) {
delay(1000) delay(1000)
if (condition()) { if (condition()) {

View File

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

View File

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

View File

@ -144,9 +144,12 @@ class UnwedgingTest : InstrumentedTest {
// - Store the olm session between A&B devices // - Store the olm session between A&B devices
// Let us pickle our session with bob here so we can later unpickle it // Let us pickle our session with bob here so we can later unpickle it
// and wedge our session. // 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 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) 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 // 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") 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) Thread.sleep(6_000)
// Force new session, and key share // Force new session, and key share
@ -207,7 +213,7 @@ class UnwedgingTest : InstrumentedTest {
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener) bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
// It's a trick to force key request on fail to decrypt // It's a trick to force key request on fail to decrypt
testHelper.doSync<Unit> { testHelper.runBlockingTest {
bobSession.cryptoService().crossSigningService() bobSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -220,7 +226,7 @@ class UnwedgingTest : InstrumentedTest {
) )
) )
} }
}, it) })
} }
// Wait until we received back the key // 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.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
@ -53,7 +54,7 @@ class XSigningTest : InstrumentedTest {
fun test_InitializeAndStoreKeys() { fun test_InitializeAndStoreKeys() {
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
testHelper.doSync<Unit> { testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(object : UserInteractiveAuthInterceptor { .initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { 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() val masterPubKey = myCrossSigningKeys?.masterKey()
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey) assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
val selfSigningKey = myCrossSigningKeys?.selfSigningKey() 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", 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) testHelper.signOutAndClose(aliceSession)
} }
@ -99,29 +105,37 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.doSync<Unit> { testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams) 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 // Check that alice can see bob keys
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true) } 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()) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey()) assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey()) 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) val myKeys = testHelper.runBlockingTest {
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey) 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()) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
@ -145,25 +159,33 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { testHelper.runBlockingTest {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
promise.resume(aliceAuthParams) 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) testHelper.runBlockingTest {
} bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
}, it) } override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
})
}
// Check that alice can see bob keys // Check that alice can see bob keys
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) } 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) 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 // 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 // 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") 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) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session // Manually mark it as trusted from first session
@ -198,7 +222,9 @@ class XSigningTest : InstrumentedTest {
fail("Alice should see the new device") 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()) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
testHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)

View File

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

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.gossiping
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.runBlocking
import org.junit.Assert import org.junit.Assert
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore import org.junit.Ignore
@ -92,7 +93,9 @@ class WithHeldTests : InstrumentedTest {
// Bob should not be able to decrypt because the keys is withheld // Bob should not be able to decrypt because the keys is withheld
try { try {
// .. might need to wait a bit for stability? // .. 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") Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) { } catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType val type = (failure as MXCryptoError.Base).errorType
@ -117,7 +120,9 @@ class WithHeldTests : InstrumentedTest {
// Previous message should still be undecryptable (partially withheld session) // Previous message should still be undecryptable (partially withheld session)
try { try {
// .. might need to wait a bit for stability? // .. 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") Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) { } catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType val type = (failure as MXCryptoError.Base).errorType
@ -164,7 +169,9 @@ class WithHeldTests : InstrumentedTest {
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
try { try {
// .. might need to wait a bit for stability? // .. 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") Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) { } catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType val type = (failure as MXCryptoError.Base).errorType
@ -222,7 +229,7 @@ class WithHeldTests : InstrumentedTest {
cryptoTestHelper.initializeCrossSigning(bobSecondSession) cryptoTestHelper.initializeCrossSigning(bobSecondSession)
// Trust bob second device from Alice POV // Trust bob second device from Alice POV
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!) aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!)
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.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.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull 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.CryptoTestHelper
import org.matrix.android.sdk.common.TestConstants 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.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -164,17 +164,22 @@ class KeysBackupTest : InstrumentedTest {
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) runBlocking {
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
}
val stateObserver = StateObserver(keysBackup, latch, 5) val stateObserver = StateObserver(keysBackup, latch, 5)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
testHelper.await(latch) testHelper.await(latch)
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false) val nbOfKeys = runBlocking {
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
}
val backedUpKeys = runBlocking {
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
}
assertEquals(2, nbOfKeys) assertEquals(2, nbOfKeys)
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
@ -833,9 +838,12 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(1, keysBackupVersionTrust.signatures.size) assertEquals(1, keysBackupVersionTrust.signatures.size)
val signature = keysBackupVersionTrust.signatures[0] val signature = keysBackupVersionTrust.signatures[0]
val device = runBlocking {
cryptoTestData.firstSession.cryptoService().getMyCryptoDevice()
}
assertTrue(signature.valid) assertTrue(signature.valid)
assertNotNull(signature.device) assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) assertEquals(device.deviceId, signature.deviceId)
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
@ -1056,7 +1064,9 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup2.isEnabled) assertFalse(keysBackup2.isEnabled)
// - Validate the old device from the new one // - Validate the old device from the new one
aliceSession2.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 // -> Backup should automatically enable on the new device
val latch4 = CountDownLatch(1) 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 val aliceUserId = cryptoTestData.firstSession.myUserId
@ -88,15 +91,21 @@ class KeysBackupTestHelper(
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
// Test check: aliceSession2 has no keys at login // 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 // Wait for backup state to be NotTrusted
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted) waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
val totalNumbersOfBackedUpKeysFromNewSession = testHelper.runBlockingTest {
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
}
return KeysBackupScenarioData(cryptoTestData, return KeysBackupScenarioData(cryptoTestData,
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys(), totalNumbersOfBackedUpKeysFromNewSession,
prepareKeysBackupDataResult, prepareKeysBackupDataResult,
aliceSession2) aliceSession2)
} }
@ -182,7 +191,10 @@ class KeysBackupTestHelper(
Assert.assertEquals(total, imported) Assert.assertEquals(total, imported)
// - The new device must have the same count of megolm keys // - 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 // - 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 android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Assert.fail import org.junit.Assert.fail
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
@ -32,9 +32,7 @@ import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.Session 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.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
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.SasVerificationTransaction 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.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService 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.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.toValue import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -75,10 +74,13 @@ class SASTest : InstrumentedTest {
} }
bobVerificationService.addListener(bobListener) bobVerificationService.addListener(bobListener)
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, val bobDevice = testHelper.runBlockingTest {
bobSession.myUserId, bobSession.cryptoService().getMyCryptoDevice()
bobSession.cryptoService().getMyDevice().deviceId, }
null) 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) assertNotNull("Alice should have a started transaction", txID)
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!) val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
@ -90,16 +92,13 @@ class SASTest : InstrumentedTest {
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID) val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
assertNotNull("Bob should have started verif transaction", bobKeyTx) assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SASDefaultVerificationTransaction) assertTrue(bobKeyTx is SasVerificationTransaction)
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx) 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) assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction? assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction? assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.state)
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
// Let's cancel from alice side // Let's cancel from alice side
val cancelLatch = CountDownLatch(1) val cancelLatch = CountDownLatch(1)
@ -107,7 +106,7 @@ class SASTest : InstrumentedTest {
val bobListener2 = object : VerificationService.Listener { val bobListener2 = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == txID) { if (tx.transactionId == txID) {
val immutableState = (tx as SASDefaultVerificationTransaction).state val immutableState = (tx as SasVerificationTransaction).state
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) { if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
cancelLatch.countDown() cancelLatch.countDown()
} }
@ -116,14 +115,16 @@ class SASTest : InstrumentedTest {
} }
bobVerificationService.addListener(bobListener2) bobVerificationService.addListener(bobListener2)
aliceSasTx.cancel(CancelCode.User) testHelper.runBlockingTest {
aliceKeyTx.cancel(CancelCode.User)
}
testHelper.await(cancelLatch) testHelper.await(cancelLatch)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled) assertTrue("Should be cancelled on alice side", aliceKeyTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled) assertTrue("Should be cancelled on bob side", bobKeyTx.state is VerificationTxState.Cancelled)
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled val bobCancelState = bobKeyTx.state as VerificationTxState.Cancelled
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe) assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
assertFalse("Should be cancelled by other on bob side", bobCancelState.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 alice side", CancelCode.User, aliceCancelState.cancelCode)
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.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) cryptoTestData.cleanUp(testHelper)
} }
@ -177,12 +175,16 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId val aliceDevice = testHelper.runBlockingTest {
aliceSession.cryptoService().getMyCryptoDevice().deviceId
}
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
(tx as IncomingSasVerificationTransaction).performAccept() testHelper.runBlockingTest {
tx.acceptVerification()
}
} }
} }
} }
@ -226,7 +228,9 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId 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) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
@ -267,7 +271,9 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId 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) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
@ -283,12 +289,15 @@ class SASTest : InstrumentedTest {
aliceUserID: String?, aliceUserID: String?,
aliceDevice: String?, aliceDevice: String?,
tid: String, tid: String,
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS, protocols: List<String> = emptyList(),
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES, hashes: List<String> = emptyList(),
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS, mac: List<String> = emptyList(),
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) { codes: List<String> = emptyList()) {
val deviceId = runBlocking {
bobSession.cryptoService().getMyCryptoDevice().deviceId
}
val startMessage = KeyVerificationStart( val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyDevice().deviceId, fromDevice = deviceId,
method = VerificationMethod.SAS.toValue(), method = VerificationMethod.SAS.toValue(),
transactionId = tid, transactionId = tid,
keyAgreementProtocols = protocols, keyAgreementProtocols = protocols,
@ -323,16 +332,16 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2) val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2) val aliceCancelledLatch = CountDownLatch(1)
val createdTx = mutableListOf<SASDefaultVerificationTransaction>() val createdTx = mutableListOf<VerificationTransaction>()
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) { override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx as SASDefaultVerificationTransaction) createdTx.add(tx)
aliceCreatedLatch.countDown() aliceCreatedLatch.countDown()
} }
override fun transactionUpdated(tx: VerificationTransaction) { 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() aliceCancelledLatch.countDown()
} }
} }
@ -340,10 +349,14 @@ class SASTest : InstrumentedTest {
aliceVerificationService.addListener(aliceListener) aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId val bobDeviceId = testHelper.runBlockingTest {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) bobSession.cryptoService().getMyCryptoDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) }
testHelper.runBlockingTest {
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), forceDownload = true)
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
}
testHelper.await(aliceCreatedLatch) testHelper.await(aliceCreatedLatch)
testHelper.await(aliceCancelledLatch) testHelper.await(aliceCancelledLatch)
@ -366,17 +379,10 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
var accepted: ValidVerificationInfoAccept? = null
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
val aliceAcceptedLatch = CountDownLatch(1) val aliceAcceptedLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}") if (tx.state is VerificationTxState.OnAccepted) {
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
val at = tx as SASDefaultVerificationTransaction
accepted = at.accepted
startReq = at.startReq
aliceAcceptedLatch.countDown() aliceAcceptedLatch.countDown()
} }
} }
@ -385,90 +391,62 @@ class SASTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}") if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
bobVerificationService.removeListener(this) bobVerificationService.removeListener(this)
val at = tx as IncomingSasVerificationTransaction testHelper.runBlockingTest {
at.performAccept() tx.acceptVerification()
}
} }
} }
} }
bobVerificationService.addListener(bobListener) bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId val bobDeviceId = runBlocking {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) bobSession.cryptoService().getMyCryptoDevice().deviceId
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))
} }
testHelper.runBlockingTest {
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
}
testHelper.await(aliceAcceptedLatch)
cryptoTestData.cleanUp(testHelper) cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun test_aliceAndBobSASCode() { fun test_aliceAndBobSASCode() {
val supportedMethods = listOf(VerificationMethod.SAS)
val testHelper = CommonTestHelper(context()) val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession!!
val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
val aliceVerificationService = aliceSession.cryptoService().verificationService() val latch = CountDownLatch(2)
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState Timber.v("Alice transactionUpdated: ${tx.state}")
when (uxState) { latch.countDown()
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
aliceSASLatch.countDown()
}
else -> Unit
}
} }
} }
aliceVerificationService.addListener(aliceListener) aliceSession.cryptoService().verificationService().addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState Timber.v("Bob transactionUpdated: ${tx.state}")
when (uxState) { latch.countDown()
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
}
else -> Unit
}
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
bobSASLatch.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 assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
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))
cryptoTestData.cleanUp(testHelper) cryptoTestData.cleanUp(testHelper)
} }
@ -478,7 +456,8 @@ class SASTest : InstrumentedTest {
val testHelper = CommonTestHelper(context()) val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
@ -487,21 +466,26 @@ class SASTest : InstrumentedTest {
val aliceSASLatch = CountDownLatch(1) val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated pr=$pr")
}
var matchOnce = true var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState Timber.v("Alice transactionUpdated: ${tx.state}")
Log.v("TEST", "== aliceState ${uxState.name}") if (tx !is SasVerificationTransaction) return
when (uxState) { when (tx.state) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
} }
OutgoingSasVerificationTransaction.UxState.VERIFIED -> { VerificationTxState.Verified -> {
if (matchOnce) { if (matchOnce) {
matchOnce = false matchOnce = false
aliceSASLatch.countDown() aliceSASLatch.countDown()
} }
} }
else -> Unit else -> Unit
} }
} }
} }
@ -511,41 +495,53 @@ class SASTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
var acceptOnce = true var acceptOnce = true
var matchOnce = true var matchOnce = true
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated: pr=$pr")
}
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState Timber.v("Bob transactionUpdated: ${tx.state}")
Log.v("TEST", "== bobState ${uxState.name}") if (tx !is SasVerificationTransaction) return
when (uxState) { when (tx.state) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { VerificationTxState.OnStarted -> testHelper.runBlockingTest {
if (acceptOnce) { if (acceptOnce) {
acceptOnce = false acceptOnce = false
tx.performAccept() tx.acceptVerification()
} }
} }
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
if (matchOnce) { if (matchOnce) {
matchOnce = false matchOnce = false
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
} }
} }
IncomingSasVerificationTransaction.UxState.VERIFIED -> { VerificationTxState.ShortCodeAccepted -> {
bobSASLatch.countDown() bobSASLatch.countDown()
} }
else -> Unit else -> Unit
} }
} }
} }
bobVerificationService.addListener(bobListener) bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId val bobDeviceId = runBlocking {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) bobSession.cryptoService().getMyCryptoDevice().deviceId
}
testHelper.runBlockingTest {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
}
testHelper.await(aliceSASLatch) testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch) testHelper.await(bobSASLatch)
// Assert that devices are verified // Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId) val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) 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("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) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.cleanUp(testHelper) cryptoTestData.cleanUp(testHelper)
@ -563,11 +559,13 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val req = aliceVerificationService.requestKeyVerificationInDMs( val req = testHelper.runBlockingTest {
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), aliceVerificationService.requestKeyVerificationInDMs(
bobSession.myUserId, listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
cryptoTestData.roomId bobSession.myUserId,
) cryptoTestData.roomId
)
}
var requestID: String? = null var requestID: String? = null
@ -590,11 +588,13 @@ class SASTest : InstrumentedTest {
} }
} }
bobVerificationService.readyPendingVerification( testHelper.runBlockingTest {
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), bobVerificationService.readyPendingVerification(
aliceSession.myUserId, listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
requestID!! aliceSession.myUserId,
) requestID!!
)
}
// wait for alice to get the ready // wait for alice to get the ready
testHelper.waitWithLatch { testHelper.waitWithLatch {
@ -606,19 +606,19 @@ class SASTest : InstrumentedTest {
} }
// Start concurrent! // Start concurrent!
aliceVerificationService.beginKeyVerificationInDMs( testHelper.runBlockingTest {
VerificationMethod.SAS, aliceVerificationService.requestKeyVerificationInDMs(
requestID!!, methods = listOf(VerificationMethod.SAS),
cryptoTestData.roomId, otherUserId = bobSession.myUserId,
bobSession.myUserId, roomId = cryptoTestData.roomId
bobSession.sessionParams.deviceId!!) )
bobVerificationService.beginKeyVerificationInDMs( bobVerificationService.requestKeyVerificationInDMs(
VerificationMethod.SAS, methods = listOf(VerificationMethod.SAS),
requestID!!, otherUserId = aliceSession.myUserId,
cryptoTestData.roomId, roomId = cryptoTestData.roomId
aliceSession.myUserId, )
aliceSession.sessionParams.deviceId!!) }
// we should reach SHOW SAS on both // we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction? 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * 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 androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBe
@ -23,19 +23,13 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest 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.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod 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.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.TestConstants import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@ -147,50 +141,19 @@ class VerificationTest : InstrumentedTest {
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true) ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
) )
// TODO Add tests without SAS
private fun doTest(aliceSupportedMethods: List<VerificationMethod>, private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
bobSupportedMethods: List<VerificationMethod>, bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult, expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult) { expectedResultForBob: ExpectedResult) {
val testHelper = CommonTestHelper(context()) val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!! val bobSession = cryptoTestData.secondSession!!
testHelper.doSync<Unit> { callback -> cryptoTestHelper.initializeCrossSigning(aliceSession)
aliceSession.cryptoService().crossSigningService() cryptoTestHelper.initializeCrossSigning(bobSession)
.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)
}
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService() val bobVerificationService = bobSession.cryptoService().verificationService()
@ -202,6 +165,7 @@ class VerificationTest : InstrumentedTest {
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request // Step 4: Alice receive the ready request
Timber.v("Alice is ready: ${pr.isReady}")
if (pr.isReady) { if (pr.isReady) {
aliceReadyPendingVerificationRequest = pr aliceReadyPendingVerificationRequest = pr
latch.countDown() latch.countDown()
@ -213,16 +177,19 @@ class VerificationTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) { override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request // Step 2: Bob accepts the verification request
bobVerificationService.readyPendingVerificationInDMs( Timber.v("Bob accepts the verification request")
bobSupportedMethods, testHelper.runBlockingTest {
aliceSession.myUserId, bobVerificationService.readyPendingVerification(
cryptoTestData.roomId, bobSupportedMethods,
pr.transactionId!! aliceSession.myUserId,
) pr.transactionId!!
)
}
} }
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready // Step 3: Bob is ready
Timber.v("Bob is ready: ${pr.isReady}")
if (pr.isReady) { if (pr.isReady) {
bobReadyPendingVerificationRequest = pr bobReadyPendingVerificationRequest = pr
latch.countDown() latch.countDown()
@ -233,7 +200,9 @@ class VerificationTest : InstrumentedTest {
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request // Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId) testHelper.runBlockingTest {
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
}
testHelper.await(latch) testHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr -> 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 android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList 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.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService 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.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.NewSessionListener import org.matrix.android.sdk.internal.crypto.NewSessionListener
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest 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.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult 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.MXEncryptEventContentResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap 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.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
interface CryptoService { interface CryptoService {
@ -49,9 +47,9 @@ interface CryptoService {
fun keysBackupService(): KeysBackupService 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 fun getCryptoVersion(context: Context, longFormat: Boolean): String
@ -61,11 +59,9 @@ interface CryptoService {
fun setWarnOnUnknownDevices(warn: Boolean) 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> suspend fun getMyCryptoDevice(): CryptoDeviceInfo
fun getMyDevice(): CryptoDeviceInfo
fun getGlobalBlacklistUnverifiedDevices(): Boolean fun getGlobalBlacklistUnverifiedDevices(): Boolean
@ -73,8 +69,6 @@ interface CryptoService {
fun setRoomUnBlacklistUnverifiedDevices(roomId: String) fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
fun getDeviceTrackingStatus(userId: String): Int
suspend fun importRoomKeys(roomKeysAsArray: ByteArray, suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
password: String, password: String,
progressListener: ProgressListener?): ImportRoomKeysResult progressListener: ProgressListener?): ImportRoomKeysResult
@ -83,7 +77,7 @@ interface CryptoService {
fun setRoomBlacklistUnverifiedDevices(roomId: String) fun setRoomBlacklistUnverifiedDevices(roomId: String)
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun reRequestRoomKeyForEvent(event: Event) fun reRequestRoomKeyForEvent(event: Event)
@ -91,29 +85,26 @@ interface CryptoService {
fun removeRoomKeysRequestListener(listener: GossipingRequestListener) fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) suspend fun fetchDevicesList(): List<DeviceInfo>
fun getMyDevicesInfo(): List<DeviceInfo> fun getMyDevicesInfo(): List<DeviceInfo>
fun getLiveMyDevicesInfo(): LiveData<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 isRoomEncrypted(roomId: String): Boolean
fun encryptEventContent(eventContent: Content, suspend fun encryptEventContent(eventContent: Content,
eventType: String, eventType: String,
roomId: String, roomId: String): MXEncryptEventContentResult
callback: MatrixCallback<MXEncryptEventContentResult>)
fun discardOutboundSession(roomId: String) fun discardOutboundSession(roomId: String)
@Throws(MXCryptoError::class) @Throws(MXCryptoError::class)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
fun getEncryptionAlgorithm(roomId: String): String? fun getEncryptionAlgorithm(roomId: String): String?
@ -121,11 +112,11 @@ interface CryptoService {
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo> 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) fun addNewSessionListener(newSessionListener: NewSessionListener)
@ -150,7 +141,7 @@ interface CryptoService {
* Perform any background tasks that can be done before a message is ready to * Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message. * 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. * 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 package org.matrix.android.sdk.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.Optional 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 * 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? // TODO this isn't used anywhere besides in tests?
// Is this the local trust concept that we have for devices? // 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. * 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 * 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. * Initialize cross signing for this user.
* Users needs to enter credentials * Users needs to enter credentials
*/ */
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
callback: MatrixCallback<Unit>)
/** /**
* Does our own user have a valid cross signing identity uploaded. * 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. * 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. * 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 * by the server and if they do so
*/ */
suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?, uskKeyPrivateKey: String?,
sskPrivateKey: String?): UserTrustResult sskPrivateKey: String?): UserTrustResult
/** /**
* Get the public cross signing keys for the given user * 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. * @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 */ /** Get our own public cross signing keys */
fun getMyCrossSigningKeys(): MXCrossSigningInfo? suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
/** Get our own private cross signing keys */ /** 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? * Can we sign our other devices or other users?
@ -93,11 +91,10 @@ interface CrossSigningService {
fun allPrivateKeysKnown(): Boolean fun allPrivateKeysKnown(): Boolean
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */ /** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
fun trustUser(otherUserId: String, suspend fun trustUser(otherUserId: String)
callback: MatrixCallback<Unit>)
/** Mark our own master key as trusted */ /** Mark our own master key as trusted */
fun markMyMasterKeyAsTrusted() suspend fun markMyMasterKeyAsTrusted()
/** /**
* Sign one of your devices and upload the signature * 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 * using the self-signing key for our own devices or using the user-signing key and the master
* key of another user. * key of another user.
*/ */
fun checkDeviceTrust(otherUserId: String, suspend fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String, otherDeviceId: String,
// TODO what is locallyTrusted used for? // TODO what is locallyTrusted used for?
locallyTrusted: Boolean?): DeviceTrustResult locallyTrusted: Boolean?): DeviceTrustResult
// FIXME Those method do not have to be in the service // FIXME Those method do not have to be in the service
// TODO those three methods doesn't seem to be used anywhere? // 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 import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
interface KeysBackupService { interface KeysBackupService {
/** /**
* Retrieve the current version of the backup from the homeserver * 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 * 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 * Facility method to get the number of backed up keys
*/ */
fun getTotalNumbersOfBackedUpKeys(): Int suspend fun getTotalNumbersOfBackedUpKeys(): Int
// /** // /**
// * Start to back up keys immediately. // * Start to back up keys immediately.
@ -71,7 +72,7 @@ interface KeysBackupService {
/** /**
* Return the current progress of the backup * 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. * 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. * 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 * 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. * Set trust on a keys backup version.
@ -199,7 +200,7 @@ interface KeysBackupService {
// For gossiping // For gossiping
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean 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 * 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 * 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 * 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 supportsEmoji(): Boolean
fun supportsDecimal(): Boolean
fun getEmojiCodeRepresentation(): List<EmojiRepresentation> fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
fun getDecimalCodeRepresentation(): String fun getDecimalCodeRepresentation(): String
@ -30,9 +28,9 @@ interface SasVerificationTransaction : VerificationTransaction {
* To be called by the client when the user has verified that * To be called by the client when the user has verified that
* both short codes do match * both short codes do match
*/ */
fun userHasVerifiedShortCode() 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 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 import org.matrix.android.sdk.api.session.events.model.LocalEcho
/** /**
@ -36,7 +35,7 @@ interface VerificationService {
/** /**
* Mark this device as verified manually * 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? fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
@ -46,54 +45,37 @@ interface VerificationService {
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
fun beginKeyVerification(method: VerificationMethod, /**
otherUserId: String, * Request key verification with another user via room events (instead of the to-device API).
otherDeviceId: String, */
transactionId: String?): String? 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>, suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest
otherUserId: String,
roomId: String,
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
fun cancelVerificationRequest(request: 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>, suspend fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String, otherUserId: String,
otherDevices: List<String>?): PendingVerificationRequest transactionId: String): Boolean
fun declineVerificationRequestInDMs(otherUserId: String, suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
transactionId: String,
roomId: String)
// Only SAS method is supported for the moment suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String)
// TODO Parameter otherDeviceId should be removed in this case
fun beginKeyVerificationInDMs(method: VerificationMethod,
transactionId: String,
roomId: String,
otherUserId: String,
otherDeviceId: String): String
/** suspend fun beginKeyVerification(method: VerificationMethod,
* Returns false if the request is unknown otherUserId: String,
*/ transactionId: String): String?
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
transactionId: String): Boolean
/** suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String?
* Returns false if the request is unknown
*/
fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean
interface Listener { interface Listener {
/** /**
@ -137,6 +119,4 @@ interface VerificationService {
return age in tooInThePast..tooInTheFuture return age in tooInThePast..tooInTheFuture
} }
} }
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
} }

View File

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

View File

@ -91,6 +91,7 @@ object EventType {
const val SEND_SECRET = "m.secret.send" const val SEND_SECRET = "m.secret.send"
// Interactive key verification // 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_START = "m.key.verification.start"
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept" const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"
const val KEY_VERIFICATION_KEY = "m.key.verification.key" 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 package org.matrix.android.sdk.api.session.room.model.message
import org.matrix.android.sdk.api.session.events.model.EventType
object MessageType { object MessageType {
const val MSGTYPE_TEXT = "m.text" const val MSGTYPE_TEXT = "m.text"
const val MSGTYPE_EMOTE = "m.emote" const val MSGTYPE_EMOTE = "m.emote"
@ -26,7 +28,7 @@ object MessageType {
const val MSGTYPE_LOCATION = "m.location" const val MSGTYPE_LOCATION = "m.location"
const val MSGTYPE_FILE = "m.file" const val MSGTYPE_FILE = "m.file"
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request" const val MSGTYPE_VERIFICATION_REQUEST = EventType.KEY_VERIFICATION_REQUEST
// Add, in local, a fake message type in order to StickerMessage can inherit Message class // Add, in local, a fake message type in order to StickerMessage can inherit Message class
// Because sticker isn't a message type but a event type without msgtype field // Because sticker isn't a message type but a event type without msgtype field

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,14 +16,14 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext 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.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod 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.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel 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.model.CryptoCrossSigningKey
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreException import uniffi.olm.CryptoStoreException
import uniffi.olm.SignatureException import uniffi.olm.SignatureException
@ -65,7 +65,7 @@ sealed class UserIdentities {
/** /**
* Convert the identity into a MxCrossSigningInfo class. * 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 userSigningKey: CryptoCrossSigningKey,
private val trustsOurOwnDevice: Boolean, private val trustsOurOwnDevice: Boolean,
private val olmMachine: OlmMachine, private val olmMachine: OlmMachine,
private val requestSender: RequestSender) : UserIdentities() { private val requestSender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
/** /**
* Our own user id. * Our own user id.
*/ */
override fun userId() = this.userId override fun userId() = userId
/** /**
* Manually verify our user identity. * Manually verify our user identity.
@ -95,8 +96,8 @@ internal class OwnUserIdentity(
*/ */
@Throws(SignatureException::class) @Throws(SignatureException::class)
override suspend fun verify() { override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request) requestSender.sendSignatureUpload(request)
} }
/** /**
@ -106,13 +107,13 @@ internal class OwnUserIdentity(
*/ */
@Throws(CryptoStoreException::class) @Throws(CryptoStoreException::class)
override suspend fun verified(): Boolean { 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. * Does the identity trust our own device.
*/ */
fun trustsOurOwnDevice() = this.trustsOurOwnDevice fun trustsOurOwnDevice() = trustsOurOwnDevice
/** /**
* Request an interactive verification to begin * Request an interactive verification to begin
@ -133,32 +134,33 @@ internal class OwnUserIdentity(
@Throws(CryptoStoreException::class) @Throws(CryptoStoreException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest { suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
val stringMethods = prepareMethods(methods) val stringMethods = prepareMethods(methods)
val result = this.olmMachine.inner().requestSelfVerification(stringMethods) val result = olmMachine.inner().requestSelfVerification(stringMethods)
this.requestSender.sendVerificationRequest(result!!.request) requestSender.sendVerificationRequest(result!!.request)
return VerificationRequest( return VerificationRequest(
this.olmMachine.inner(), machine = olmMachine.inner(),
result.verification, inner = result.verification,
this.requestSender, sender = requestSender,
this.olmMachine.verificationListeners coroutineDispatchers = coroutineDispatchers,
listeners = olmMachine.verificationListeners
) )
} }
/** /**
* Convert the identity into a MxCrossSigningInfo class. * Convert the identity into a MxCrossSigningInfo class.
*/ */
override fun toMxCrossSigningInfo(): MXCrossSigningInfo { override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
val masterKey = this.masterKey val masterKey = masterKey
val selfSigningKey = this.selfSigningKey val selfSigningKey = selfSigningKey
val userSigningKey = this.userSigningKey val userSigningKey = userSigningKey
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false) val trustLevel = DeviceTrustLevel(verified(), false)
// TODO remove this, this is silly, we have way too many methods to check if a user is verified // TODO remove this, this is silly, we have way too many methods to check if a user is verified
masterKey.trustLevel = trustLevel masterKey.trustLevel = trustLevel
selfSigningKey.trustLevel = trustLevel selfSigningKey.trustLevel = trustLevel
userSigningKey.trustLevel = trustLevel userSigningKey.trustLevel = trustLevel
val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey) 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 masterKey: CryptoCrossSigningKey,
private val selfSigningKey: CryptoCrossSigningKey, private val selfSigningKey: CryptoCrossSigningKey,
private val olmMachine: OlmMachine, 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. * 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. * Manually verify this user identity.
@ -189,8 +192,8 @@ internal class UserIdentity(
*/ */
@Throws(SignatureException::class) @Throws(SignatureException::class)
override suspend fun verify() { override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request) requestSender.sendSignatureUpload(request)
} }
/** /**
@ -199,7 +202,7 @@ internal class UserIdentity(
* @return True if the identity is considered to be verified and trusted, false otherwise. * @return True if the identity is considered to be verified and trusted, false otherwise.
*/ */
override suspend fun verified(): Boolean { 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 transactionId: String
): VerificationRequest { ): VerificationRequest {
val stringMethods = prepareMethods(methods) 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( return VerificationRequest(
this.olmMachine.inner(), machine = olmMachine.inner(),
innerRequest, inner = innerRequest,
this.requestSender, sender = requestSender,
this.olmMachine.verificationListeners coroutineDispatchers = coroutineDispatchers,
listeners = olmMachine.verificationListeners
) )
} }
/** /**
* Convert the identity into a MxCrossSigningInfo class. * Convert the identity into a MxCrossSigningInfo class.
*/ */
override fun toMxCrossSigningInfo(): MXCrossSigningInfo { override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
// val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey) // val crossSigningKeys = listOf(masterKey, selfSigningKey)
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false) val trustLevel = DeviceTrustLevel(verified(), false)
// TODO remove this, this is silly, we have way too many methods to check if a user is verified // TODO remove this, this is silly, we have way too many methods to check if a user is verified
masterKey.trustLevel = trustLevel masterKey.trustLevel = trustLevel
selfSigningKey.trustLevel = trustLevel selfSigningKey.trustLevel = trustLevel
return MXCrossSigningInfo(this.userId, listOf( return MXCrossSigningInfo(userId, listOf(
this.masterKey.also { it.trustLevel = trustLevel }, masterKey.also { it.trustLevel = trustLevel },
this.selfSigningKey.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.Handler
import android.os.Looper import android.os.Looper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext 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.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady 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.api.session.crypto.verification.safeValueOf
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding 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.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 org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import timber.log.Timber import timber.log.Timber
import uniffi.olm.OlmMachine import uniffi.olm.OlmMachine
@ -45,6 +46,7 @@ internal class VerificationRequest(
private val machine: OlmMachine, private val machine: OlmMachine,
private var inner: VerificationRequest, private var inner: VerificationRequest,
private val sender: RequestSender, private val sender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val listeners: ArrayList<VerificationService.Listener> private val listeners: ArrayList<VerificationService.Listener>
) { ) {
private val uiHandler = Handler(Looper.getMainLooper()) private val uiHandler = Handler(Looper.getMainLooper())
@ -53,7 +55,7 @@ internal class VerificationRequest(
uiHandler.post { uiHandler.post {
listeners.forEach { listeners.forEach {
try { try {
it.verificationRequestUpdated(this.toPendingVerificationRequest()) it.verificationRequestUpdated(toPendingVerificationRequest())
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners") Timber.e(e, "## Error while notifying listeners")
} }
@ -68,12 +70,12 @@ internal class VerificationRequest(
* event that initiated the flow. * event that initiated the flow.
*/ */
internal fun flowId(): String { 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 */ /** The user ID of the other user that is participating in this verification flow */
internal fun otherUser(): String { 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 /** 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? { internal fun otherDeviceId(): String? {
refreshData() refreshData()
return this.inner.otherDeviceId return inner.otherDeviceId
} }
/** Did we initiate this verification flow */ /** Did we initiate this verification flow */
internal fun weStarted(): Boolean { internal fun weStarted(): Boolean {
return this.inner.weStarted return inner.weStarted
} }
/** Get the id of the room where this verification is happening /** 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. * Will be null if the verification is not happening inside a room.
*/ */
internal fun roomId(): String? { internal fun roomId(): String? {
return this.inner.roomId return inner.roomId
} }
/** Did the non-initiating side respond with a m.key.verification.read event /** Did the non-initiating side respond with a m.key.verification.read event
@ -107,13 +109,13 @@ internal class VerificationRequest(
*/ */
internal fun isReady(): Boolean { internal fun isReady(): Boolean {
refreshData() refreshData()
return this.inner.isReady return inner.isReady
} }
/** Did we advertise that we're able to scan QR codes */ /** Did we advertise that we're able to scan QR codes */
internal fun canScanQrCodes(): Boolean { internal fun canScanQrCodes(): Boolean {
refreshData() 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 /** Accept the verification request advertising the given methods as supported
@ -132,15 +134,15 @@ internal class VerificationRequest(
suspend fun acceptWithMethods(methods: List<VerificationMethod>) { suspend fun acceptWithMethods(methods: List<VerificationMethod>) {
val stringMethods = prepareMethods(methods) val stringMethods = prepareMethods(methods)
val request = this.machine.acceptVerificationRequest( val request = machine.acceptVerificationRequest(
this.inner.otherUserId, inner.otherUserId,
this.inner.flowId, inner.flowId,
stringMethods stringMethods
) )
if (request != null) { if (request != null) {
this.sender.sendVerificationRequest(request) sender.sendVerificationRequest(request)
this.dispatchRequestUpdated() dispatchRequestUpdated()
} }
} }
@ -158,12 +160,12 @@ internal class VerificationRequest(
* emoji verification, or null if we can't yet transition into emoji verification. * emoji verification, or null if we can't yet transition into emoji verification.
*/ */
internal suspend fun startSasVerification(): SasVerification? { internal suspend fun startSasVerification(): SasVerification? {
return withContext(Dispatchers.IO) { return withContext(coroutineDispatchers.io) {
val result = machine.startSasVerification(inner.otherUserId, inner.flowId) val result = machine.startSasVerification(inner.otherUserId, inner.flowId)
if (result != null) { if (result != null) {
sender.sendVerificationRequest(result.request) sender.sendVerificationRequest(result.request)
SasVerification(machine, result.sas, sender, listeners) SasVerification(machine, result.sas, sender, coroutineDispatchers, listeners)
} else { } else {
null null
} }
@ -187,10 +189,10 @@ internal class VerificationRequest(
// TODO again, what's the deal with ISO_8859_1? // TODO again, what's the deal with ISO_8859_1?
val byteArray = data.toByteArray(Charsets.ISO_8859_1) val byteArray = data.toByteArray(Charsets.ISO_8859_1)
val encodedData = byteArray.toBase64NoPadding() 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) sender.sendVerificationRequest(result.request)
return QrCodeVerification(this.machine, this, result.qr, this.sender, this.listeners) return QrCodeVerification(machine, this, result.qr, sender, coroutineDispatchers, listeners)
} }
/** Transition into a QR code verification to display a QR code /** 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. * QR code verification, or null if we can't yet transition into QR code verification.
*/ */
internal fun startQrVerification(): QrCodeVerification? { 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) { return if (qrcode != null) {
QrCodeVerification( QrCodeVerification(
this.machine, machine = machine,
this, request = this,
qrcode, inner = qrcode,
this.sender, sender = sender,
this.listeners, coroutineDispatchers = coroutineDispatchers,
listeners = listeners,
) )
} else { } else {
null null
@ -237,24 +240,24 @@ internal class VerificationRequest(
* The method turns into a noop, if the verification flow has already been cancelled. * The method turns into a noop, if the verification flow has already been cancelled.
*/ */
internal suspend fun cancel() { internal suspend fun cancel() {
val request = this.machine.cancelVerification( val request = machine.cancelVerification(
this.inner.otherUserId, inner.otherUserId,
this.inner.flowId, inner.flowId,
CancelCode.User.value CancelCode.User.value
) )
if (request != null) { if (request != null) {
this.sender.sendVerificationRequest(request) sender.sendVerificationRequest(request)
this.dispatchRequestUpdated() dispatchRequestUpdated()
} }
} }
/** Fetch fresh data from the Rust side for our verification flow */ /** Fetch fresh data from the Rust side for our verification flow */
private fun refreshData() { 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) { if (request != null) {
this.inner = request inner = request
} }
} }
@ -269,7 +272,7 @@ internal class VerificationRequest(
*/ */
internal fun toPendingVerificationRequest(): PendingVerificationRequest { internal fun toPendingVerificationRequest(): PendingVerificationRequest {
refreshData() refreshData()
val cancelInfo = this.inner.cancelInfo val cancelInfo = inner.cancelInfo
val cancelCode = val cancelCode =
if (cancelInfo != null) { if (cancelInfo != null) {
safeValueOf(cancelInfo.cancelCode) safeValueOf(cancelInfo.cancelCode)
@ -277,72 +280,72 @@ internal class VerificationRequest(
null null
} }
val ourMethods = this.inner.ourMethods val ourMethods = inner.ourMethods
val theirMethods = this.inner.theirMethods val theirMethods = inner.theirMethods
val otherDeviceId = this.inner.otherDeviceId val otherDeviceId = inner.otherDeviceId
var requestInfo: ValidVerificationInfoRequest? = null var requestInfo: ValidVerificationInfoRequest? = null
var readyInfo: ValidVerificationInfoReady? = null var readyInfo: ValidVerificationInfoReady? = null
if (this.inner.weStarted && ourMethods != null) { if (inner.weStarted && ourMethods != null) {
requestInfo = requestInfo =
ValidVerificationInfoRequest( ValidVerificationInfoRequest(
this.inner.flowId, transactionId = inner.flowId,
this.machine.deviceId(), fromDevice = machine.deviceId(),
ourMethods, methods = ourMethods,
null, timestamp = null,
) )
} else if (!this.inner.weStarted && ourMethods != null) { } else if (!inner.weStarted && ourMethods != null) {
readyInfo = readyInfo =
ValidVerificationInfoReady( ValidVerificationInfoReady(
this.inner.flowId, transactionId = inner.flowId,
this.machine.deviceId(), fromDevice = machine.deviceId(),
ourMethods, methods = ourMethods,
) )
} }
if (this.inner.weStarted && theirMethods != null && otherDeviceId != null) { if (inner.weStarted && theirMethods != null && otherDeviceId != null) {
readyInfo = readyInfo =
ValidVerificationInfoReady( ValidVerificationInfoReady(
this.inner.flowId, transactionId = inner.flowId,
otherDeviceId, fromDevice = otherDeviceId,
theirMethods, methods = theirMethods,
) )
} else if (!this.inner.weStarted && theirMethods != null && otherDeviceId != null) { } else if (!inner.weStarted && theirMethods != null && otherDeviceId != null) {
requestInfo = requestInfo =
ValidVerificationInfoRequest( ValidVerificationInfoRequest(
this.inner.flowId, transactionId = inner.flowId,
otherDeviceId, fromDevice = otherDeviceId,
theirMethods, methods = theirMethods,
System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
) )
} }
return PendingVerificationRequest( return PendingVerificationRequest(
// Creation time // Creation time
System.currentTimeMillis(), ageLocalTs = System.currentTimeMillis(),
// Who initiated the request // Who initiated the request
!this.inner.weStarted, isIncoming = !inner.weStarted,
// Local echo id, what to do here? // Local echo id, what to do here?
this.inner.flowId, localId = inner.flowId,
// other user // other user
this.inner.otherUserId, otherUserId = inner.otherUserId,
// room id // room id
this.inner.roomId, roomId = inner.roomId,
// transaction id // transaction id
this.inner.flowId, transactionId = inner.flowId,
// val requestInfo: ValidVerificationInfoRequest? = null, // val requestInfo: ValidVerificationInfoRequest? = null,
requestInfo, requestInfo = requestInfo,
// val readyInfo: ValidVerificationInfoReady? = null, // val readyInfo: ValidVerificationInfoReady? = null,
readyInfo, readyInfo = readyInfo,
// cancel code if there is one // cancel code if there is one
cancelCode, cancelConclusion = cancelCode,
// are we done/successful // are we done/successful
this.inner.isDone, isSuccessful = inner.isDone,
// did another device answer the request // did another device answer the request
this.inner.isPassive, handledByOtherSession = inner.isPassive,
// devices that should receive the events we send out // 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.VisibleForTesting
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll 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.MegolmSessionData
import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider 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.KeysBackupVersionTrust
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo 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.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody 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.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.crypto.store.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
@ -95,7 +94,7 @@ internal class RustKeyBackupService @Inject constructor(
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null // 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 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 keyCountOnServer = keysBackupVersion?.count ?: return false
val keyCountLocally = getTotalNumbersOfKeys() val keyCountLocally = getTotalNumbersOfKeys()
@ -246,11 +245,11 @@ internal class RustKeyBackupService @Inject constructor(
return keyCountLocally < keyCountOnServer return keyCountLocally < keyCountOnServer
} }
override fun getTotalNumbersOfKeys(): Int { override suspend fun getTotalNumbersOfKeys(): Int {
return olmMachine.roomKeyCounts().total.toInt() return olmMachine.roomKeyCounts().total.toInt()
} }
override fun getTotalNumbersOfBackedUpKeys(): Int { override suspend fun getTotalNumbersOfBackedUpKeys(): Int {
return olmMachine.roomKeyCounts().backedUp.toInt() 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 backedUpKeys = getTotalNumbersOfBackedUpKeys()
val total = getTotalNumbersOfKeys() val total = getTotalNumbersOfKeys()
@ -490,7 +489,7 @@ internal class RustKeyBackupService @Inject constructor(
val data = getKeys(sessionId, roomId, keysVersionResult.version) val data = getKeys(sessionId, roomId, keysVersionResult.version)
return withContext(coroutineDispatchers.computation) { return withContext(coroutineDispatchers.computation) {
withContext(Dispatchers.Main) { withContext(coroutineDispatchers.main) {
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size)) stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
} }
// Decrypting by chunk of 500 keys in parallel // Decrypting by chunk of 500 keys in parallel
@ -513,7 +512,7 @@ internal class RustKeyBackupService @Inject constructor(
.awaitAll() .awaitAll()
.flatten() .flatten()
withContext(Dispatchers.Main) { withContext(coroutineDispatchers.main) {
val stepProgress = StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size) val stepProgress = StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size)
stepProgressListener?.onStepProgress(stepProgress) stepProgressListener?.onStepProgress(stepProgress)
} }
@ -532,7 +531,7 @@ internal class RustKeyBackupService @Inject constructor(
val progressListener = if (stepProgressListener != null) { val progressListener = if (stepProgressListener != null) {
object : ProgressListener { object : ProgressListener {
override fun onProgress(progress: Int, total: Int) { override fun onProgress(progress: Int, total: Int) {
cryptoCoroutineScope.launch(Dispatchers.Main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val stepProgress = StepProgressListener.Step.ImportingKey(progress, total) val stepProgress = StepProgressListener.Step.ImportingKey(progress, total)
stepProgressListener.onStepProgress(stepProgress) stepProgressListener.onStepProgress(stepProgress)
} }
@ -581,16 +580,12 @@ internal class RustKeyBackupService @Inject constructor(
} }
override suspend fun getVersion(version: String): KeysVersionResult? { override suspend fun getVersion(version: String): KeysVersionResult? {
return withContext(coroutineDispatchers.io) { return sender.getKeyBackupVersion(version)
sender.getKeyBackupVersion(version)
}
} }
@Throws @Throws
override suspend fun getCurrentVersion(): KeysVersionResult? { override suspend fun getCurrentVersion(): KeysVersionResult? {
return withContext(coroutineDispatchers.io) { return sender.getKeyBackupVersion()
sender.getKeyBackupVersion()
}
} }
override suspend fun forceUsingLastVersion(): Boolean { override suspend fun forceUsingLastVersion(): Boolean {
@ -646,18 +641,16 @@ internal class RustKeyBackupService @Inject constructor(
Timber.w("checkAndStartKeysBackup: invalid state: $state") Timber.w("checkAndStartKeysBackup: invalid state: $state")
return@withContext return@withContext
} }
keysBackupVersion = null keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
try {
withContext(coroutineDispatchers.io) { val data = getCurrentVersion()
try { withContext(coroutineDispatchers.crypto) {
val data = getCurrentVersion() checkAndStartWithKeysBackupVersion(data)
withContext(coroutineDispatchers.crypto) { }
checkAndStartWithKeysBackupVersion(data) } catch (failure: Throwable) {
} Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
} catch (failure: Throwable) { withContext(coroutineDispatchers.crypto) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
keysBackupStateManager.state = KeysBackupState.Unknown 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 val info = olmMachine.getBackupKeys() ?: return null
return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion) return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
} }
@ -878,7 +871,7 @@ internal class RustKeyBackupService @Inject constructor(
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (failure is Failure.ServerError) { if (failure is Failure.ServerError) {
withContext(Dispatchers.Main) { withContext(coroutineDispatchers.main) {
Timber.e(failure, "backupKeys: backupKeys failed.") Timber.e(failure, "backupKeys: backupKeys failed.")
when (failure.error.code) { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,8 +14,9 @@
* limitations under the License. * 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 com.squareup.moshi.Types
import dagger.Lazy import dagger.Lazy
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor 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.KeysQueryResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse 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.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.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask 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.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
import org.matrix.android.sdk.internal.session.room.send.SendResponse
import timber.log.Timber import timber.log.Timber
import uniffi.olm.OutgoingVerificationRequest import uniffi.olm.OutgoingVerificationRequest
import uniffi.olm.Request import uniffi.olm.Request
@ -80,6 +83,7 @@ internal class RequestSender @Inject constructor(
private val getSessionsDataTask: GetSessionsDataTask, private val getSessionsDataTask: GetSessionsDataTask,
private val getRoomSessionsDataTask: GetRoomSessionsDataTask, private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
private val getRoomSessionDataTask: GetRoomSessionDataTask, private val getRoomSessionDataTask: GetRoomSessionDataTask,
private val moshi: Moshi
) { ) {
companion object { companion object {
const val REQUEST_RETRY_COUNT = 3 const val REQUEST_RETRY_COUNT = 3
@ -97,16 +101,16 @@ internal class RequestSender @Inject constructor(
suspend fun queryKeys(request: Request.KeysQuery): String { suspend fun queryKeys(request: Request.KeysQuery): String {
val params = DownloadKeysForUsersTask.Params(request.users, null) val params = DownloadKeysForUsersTask.Params(request.users, null)
val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT) 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)!! return adapter.toJson(response)!!
} }
suspend fun uploadKeys(request: Request.KeysUpload): String { 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 params = UploadKeysTask.Params(body)
val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) 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)!! 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) return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
} }
suspend fun sendRoomMessage(request: Request.RoomMessage): String { 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 { suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): SendResponse {
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java) val paramsAdapter = moshi.adapter<Content>(Map::class.java)
val jsonContent = adapter.fromJson(content) val jsonContent = paramsAdapter.fromJson(content)
val event = Event(eventType, transactionId, jsonContent, roomId = roomId) val event = Event(eventType, transactionId, jsonContent, roomId = roomId)
val params = SendVerificationMessageTask.Params(event) 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) { suspend fun sendSignatureUpload(request: Request.SignatureUpload): String {
sendSignatureUpload(request.body) return sendSignatureUpload(request.body)
} }
suspend fun sendSignatureUpload(request: SignatureUploadRequest) { suspend fun sendSignatureUpload(request: SignatureUploadRequest): String {
sendSignatureUpload(request.body) return sendSignatureUpload(request.body)
} }
private suspend fun sendSignatureUpload(body: String) { private suspend fun sendSignatureUpload(body: String): String {
val adapter = MoshiProvider.providesMoshi().adapter<Map<String, Map<String, Any>>>(Map::class.java) val paramsAdapter = moshi.adapter<Map<String, Map<String, Any>>>(Map::class.java)
val signatures = adapter.fromJson(body)!! val signatures = paramsAdapter.fromJson(body)!!
val params = UploadSignaturesTask.Params(signatures) 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( suspend fun uploadCrossSigningKeys(
request: UploadSigningKeysRequest, request: UploadSigningKeysRequest,
interactiveAuthInterceptor: UserInteractiveAuthInterceptor? 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 masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel() val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.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) { suspend fun sendToDevice(eventType: String, body: String, transactionId: String) {
val adapter = MoshiProvider val adapter = moshi
.providesMoshi()
.newBuilder() .newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY) .add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build() .build()
@ -252,7 +259,7 @@ internal class RequestSender @Inject constructor(
val keys = adapter.fromJson(request.rooms)!! val keys = adapter.fromJson(request.rooms)!!
val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys)) val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys))
val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) 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)!! 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.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM 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.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.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitCallback
import javax.inject.Inject import javax.inject.Inject
internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> { internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
data class Params(val roomId: String, data class Params(val roomId: String,
val event: Event, 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
) )
} }
@ -51,52 +47,34 @@ internal class DefaultEncryptEventTask @Inject constructor(
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING) localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
params.keepKeys?.forEach {
localMutableContent.remove(it)
}
// try {
// let it throws // let it throws
awaitCallback<MXEncryptEventContentResult> { val result = cryptoService.encryptEventContent(localEvent.content ?: emptyMap(), localEvent.type, params.roomId)
cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it) // Better handling of local echo, to avoid decrypting transition on remote echo
}.let { result -> // Should I only do it for text messages?
val modifiedContent = HashMap(result.eventContent) val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
params.keepKeys?.forEach { toKeep -> MXEventDecryptionResult(
localEvent.content?.get(toKeep)?.let { clearEvent = Event(
// put it back in the encrypted thing type = localEvent.type,
modifiedContent[toKeep] = it content = localEvent.content,
} roomId = localEvent.roomId
} ).toContent(),
val safeResult = result.copy(eventContent = modifiedContent) forwardingCurve25519KeyChain = emptyList(),
// Better handling of local echo, to avoid decrypting transition on remote echo senderCurve25519Key = result.eventContent["sender_key"] as? String,
// Should I only do it for text messages? claimedEd25519Key = cryptoService.getMyCryptoDevice().fingerprint()
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
) )
} 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()) { if (params.encrypt && !params.event.isEncrypted()) {
return encryptEventTask.execute(EncryptEventTask.Params( return encryptEventTask.execute(EncryptEventTask.Params(
params.event.roomId ?: "", params.event.roomId ?: "",
params.event, params.event
listOf("m.relates_to")
)) ))
} }
return 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.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI 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.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.task.Task
import org.matrix.android.sdk.internal.util.toMatrixErrorStr import org.matrix.android.sdk.internal.util.toMatrixErrorStr
import javax.inject.Inject import javax.inject.Inject
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> { internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
data class Params( data class Params(
val event: Event val event: Event
) )
@ -39,10 +40,9 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask { 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 event = handleEncryption(params)
val localId = event.eventId!! val localId = event.eventId!!
try { try {
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING) localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
val response = executeRequest(globalErrorReceiver) { val response = executeRequest(globalErrorReceiver) {
@ -54,7 +54,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
) )
} }
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT) localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
return response.eventId return response
} catch (e: Throwable) { } catch (e: Throwable) {
localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr()) localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr())
throw e throw e
@ -67,7 +67,6 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
return encryptEventTask.execute(EncryptEventTask.Params( return encryptEventTask.execute(EncryptEventTask.Params(
params.event.roomId ?: "", params.event.roomId ?: "",
params.event, params.event,
listOf("m.relates_to")
)) ))
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
// We said it's ok to send verification request in clear // 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 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.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.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, Unit> { internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, SignatureUploadResponse> {
data class Params( data class Params(
val signatures: Map<String, Map<String, Any>> val signatures: Map<String, Map<String, Any>>
) )
@ -33,21 +33,13 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver
) : UploadSignaturesTask { ) : UploadSignaturesTask {
override suspend fun execute(params: UploadSignaturesTask.Params) { override suspend fun execute(params: UploadSignaturesTask.Params): SignatureUploadResponse {
try { return executeRequest(
val response = executeRequest( globalErrorReceiver,
globalErrorReceiver, canRetry = true,
canRetry = true, maxRetriesCount = 10
maxRetriesCount = 10 ) {
) { cryptoApi.uploadSignatures(params.signatures)
cryptoApi.uploadSignatures(params.signatures)
}
if (response.failures?.isNotEmpty() == true) {
throw Throwable(response.failures.toString())
}
return
} catch (f: Failure) {
throw f
} }
} }
} }

View File

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

View File

@ -43,6 +43,7 @@ internal class RealmSendingEventsDataSource(
private var roomEntity: RoomEntity? = null private var roomEntity: RoomEntity? = null
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private val builtEvents = ArrayList<TimelineEvent>()
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events -> private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) 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.Realm
import io.realm.RealmConfiguration 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.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.Event
@ -126,7 +127,9 @@ internal class TimelineEventDecryptor @Inject constructor(
return return
} }
try { try {
val result = cryptoService.decryptEvent(request.event, timelineId) val result = runBlocking {
cryptoService.decryptEvent(request.event, timelineId)
}
Timber.v("Successfully decrypted event ${event.eventId}") Timber.v("Successfully decrypted event ${event.eventId}")
realm.executeTransaction { realm.executeTransaction {
val eventId = event.eventId ?: return@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 dagger.Lazy
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject 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.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event 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.EventType
@ -343,7 +344,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
return roomEntity return roomEntity
} }
private suspend fun handleTimelineEvents(realm: Realm, private fun handleTimelineEvents(realm: Realm,
roomId: String, roomId: String,
roomEntity: RoomEntity, roomEntity: RoomEntity,
eventList: List<Event>, eventList: List<Event>,
@ -458,7 +459,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private fun decryptIfNeeded(event: Event, roomId: String) { private fun decryptIfNeeded(event: Event, roomId: String) {
try { try {
// Event from sync does not have roomId, so add it to the event first // 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( event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,
senderKey = result.senderCurve25519Key, senderKey = result.senderCurve25519Key,

View File

@ -134,10 +134,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
onView(withId(R.id.bottomSheetFragmentContainer)) onView(withId(R.id.bottomSheetFragmentContainer))
.check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session))))) .check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
val request = existingSession!!.cryptoService().verificationService().requestKeyVerification( val request = existingSession!!.cryptoService().verificationService().requestSelfKeyVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
existingSession!!.myUserId,
listOf(uiSession.sessionParams.deviceId!!)
) )
val transactionId = request.transactionId!! 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 * 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 && return cryptoService().inboundGroupSessionsCount(false) > 0 &&
cryptoService().keysBackupService().state != KeysBackupState.ReadyToBackUp cryptoService().keysBackupService().state != KeysBackupState.ReadyToBackUp
} }
fun Session.cannotLogoutSafely(): Boolean { suspend fun Session.cannotLogoutSafely(): Boolean {
// has some encrypted chat // has some encrypted chat
return hasUnsavedKeys() || return hasUnsavedKeys() ||
// has local cross signing keys // 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, data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async<KeysBackupVersionTrust> = Uninitialized,
val keysBackupState: KeysBackupState? = null, val keysBackupState: KeysBackupState? = null,
val keysBackupVersion: KeysVersionResult? = null, val keysBackupVersion: KeysVersionResult? = null,
val remainingKeysToBackup: Int = 0,
val deleteBackupRequest: Async<Unit> = Uninitialized) : val deleteBackupRequest: Async<Unit> = Uninitialized) :
MavericksState MavericksState

View File

@ -124,10 +124,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
style(ItemStyle.BIG_TEXT) style(ItemStyle.BIG_TEXT)
hasIndeterminateProcess(true) hasIndeterminateProcess(true)
val totalKeys = host.session.cryptoService().inboundGroupSessionsCount(false) val remainingKeysToBackup = data.remainingKeysToBackup
val backedUpKeys = host.session.cryptoService().inboundGroupSessionsCount(true)
val remainingKeysToBackup = totalKeys - backedUpKeys
if (data.keysBackupVersionTrust()?.usable == false) { if (data.keysBackupVersionTrust()?.usable == false) {
description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence()) 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.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory 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.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import timber.log.Timber
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
session: Session session: Session
@ -46,6 +44,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
companion object : MavericksViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> by hiltMavericksViewModelFactory()
private val cryptoService = session.cryptoService()
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService() private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
init { init {
@ -75,34 +74,12 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
private fun getKeysBackupTrust() = withState { state -> private fun getKeysBackupTrust() = withState { state ->
val versionResult = keysBackupService.keysBackupVersion val versionResult = keysBackupService.keysBackupVersion
Timber.d("BACKUP: HEEEEEEE $versionResult ${state.keysBackupVersionTrust}")
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) { if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
setState { setState { copy(deleteBackupRequest = Uninitialized) }
copy( suspend {
keysBackupVersionTrust = Loading(), keysBackupService.getKeysBackupTrust(versionResult)
deleteBackupRequest = Uninitialized }.execute {
) copy(keysBackupVersionTrust = it)
}
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)
)
}
}
} }
} }
} }
@ -119,10 +96,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
keysBackupVersion = keysBackupService.keysBackupVersion keysBackupVersion = keysBackupService.keysBackupVersion
) )
} }
when (newState) {
KeysBackupState.BackingUp, KeysBackupState.WillBackUp -> updateKeysCount()
else -> Unit
}
getKeysBackupTrust() 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() { private fun deleteCurrentBackup() {
val keysBackupService = keysBackupService val keysBackupService = keysBackupService

View File

@ -128,7 +128,7 @@ class KeyRequestHandler @Inject constructor(
} }
if (deviceInfo.isUnknown) { 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) 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 // 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 // if not they could be accepted in the middle of the reset process
// and cause strange use cases // and cause strange use cases
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach { viewModelScope.launch {
session.cryptoService().verificationService().cancelVerificationRequest(it) session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
session.cryptoService().verificationService().cancelVerificationRequest(it)
}
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
} }
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
} }
private fun handleResetAll() { 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.api.session.securestorage.SsssKeySpec
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding 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.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -91,12 +90,7 @@ class BootstrapCrossSigningTask @Inject constructor(
) )
try { try {
awaitCallback<Unit> { crossSigningService.initializeCrossSigning(params.userInteractiveAuthInterceptor)
crossSigningService.initializeCrossSigning(
params.userInteractiveAuthInterceptor,
it
)
}
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) { if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
return BootstrapResult.SuccessCrossSigningOnly 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.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert 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.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -42,7 +44,8 @@ import javax.inject.Singleton
class IncomingVerificationRequestHandler @Inject constructor( class IncomingVerificationRequestHandler @Inject constructor(
private val context: Context, private val context: Context,
private var avatarRenderer: Provider<AvatarRenderer>, 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 private var session: Session? = null
@ -61,7 +64,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
// TODO maybe check also if // TODO maybe check also if
val uid = "kvr_${tx.transactionId}" val uid = "kvr_${tx.transactionId}"
when (tx.state) { when (tx.state) {
is VerificationTxState.OnStarted -> { is VerificationTxState.OnStarted -> {
// Add a notification for every incoming request // Add a notification for every incoming request
val user = session?.getUser(tx.otherUserId) val user = session?.getUser(tx.otherUserId)
val name = user?.toMatrixItem()?.getBestName() ?: 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) it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
} }
} }
dismissedAction = Runnable { dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
tx.cancel() tx.cancel()
} }
addButton( addButton(
context.getString(R.string.action_ignore), context.getString(R.string.action_ignore),
{ tx.cancel() } LaunchCoroutineRunnable(coroutineScope) {
tx.cancel()
}
) )
addButton( addButton(
context.getString(R.string.action_open), context.getString(R.string.action_open),
@ -160,10 +165,9 @@ class IncomingVerificationRequestHandler @Inject constructor(
} }
} }
} }
dismissedAction = Runnable { dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs(pr.otherUserId, session?.cryptoService()?.verificationService()?.cancelVerificationRequest(pr.otherUserId,
pr.transactionId ?: "", pr.transactionId ?: ""
pr.roomId ?: ""
) )
} }
colorAttribute = R.attr.vctr_notice_secondary 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) = private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
"verificationRequest_${pr.transactionId}" "verificationRequest_${pr.transactionId}"
} }

View File

@ -16,8 +16,6 @@
package im.vector.app.features.crypto.verification package im.vector.app.features.crypto.verification
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
@ -131,34 +129,37 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction
} }
val hasAnyOtherSession = session.cryptoService() viewModelScope.launch {
.getCryptoDeviceInfo(session.myUserId)
.any {
it.deviceId != session.sessionParams.deviceId
}
setState { val hasAnyOtherSession = session.cryptoService()
copy( .getCryptoDeviceInfoList(session.myUserId)
otherUserMxItem = userItem?.toMatrixItem(), .any {
sasTransactionState = sasTx?.state, it.deviceId != session.sessionParams.deviceId
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) { setState {
// TODO, can I be here in DM mode? in this case should test if roomID is null? copy(
session.cryptoService().verificationService() otherUserMxItem = userItem?.toMatrixItem(),
.readyPendingVerification( sasTransactionState = sasTx?.state,
supportedVerificationMethodsProvider.provide(), qrTransactionState = qrTx?.state,
pr!!.otherUserId, transactionId = pr?.transactionId ?: initialState.verificationId,
pr.transactionId ?: "" 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) { private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
session.cryptoService() viewModelScope.launch {
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let { session.cryptoService()
session.cryptoService().verificationService().cancelVerificationRequest(it) .verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
} session.cryptoService().verificationService().cancelVerificationRequest(it)
session.cryptoService() }
.verificationService() session.cryptoService()
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "") .verificationService()
?.cancel(CancelCode.User) .getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
?.cancel(CancelCode.User)
}
} }
fun continueFromCancel() { fun continueFromCancel() {
@ -232,109 +235,25 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
when (action) { when (action) {
is VerificationAction.RequestVerificationByDM -> { is VerificationAction.RequestVerificationByDM -> {
if (roomId == null) { handleRequestVerificationByDM(roomId, otherUserId)
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
} }
is VerificationAction.StartSASVerification -> { is VerificationAction.StartSASVerification -> {
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId) handleStartSASVerification(otherUserId, action)
?: 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
} }
is VerificationAction.RemoteQrCodeScanned -> { is VerificationAction.RemoteQrCodeScanned -> {
val existingTransaction = session.cryptoService().verificationService() handleRemoteQrCodeScanned(action)
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.userHasScannedOtherQrCode(action.scannedData)
} }
is VerificationAction.OtherUserScannedSuccessfully -> { is VerificationAction.OtherUserScannedSuccessfully -> {
val transactionId = state.transactionId ?: return@withState handleOtherUserScannedSuccessfully(state.transactionId, otherUserId)
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.otherUserScannedMyQrCode()
} }
is VerificationAction.OtherUserDidNotScanned -> { is VerificationAction.OtherUserDidNotScanned -> {
val transactionId = state.transactionId ?: return@withState handleOtherUserDidNotScanned(state.transactionId, otherUserId)
val existingTransaction = session.cryptoService().verificationService()
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
existingTransaction
?.otherUserDidNotScannedMyQrCode()
} }
is VerificationAction.SASMatchAction -> { is VerificationAction.SASMatchAction -> {
(session.cryptoService().verificationService() handleSASMatchAction(action)
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
} }
is VerificationAction.SASDoNotMatchAction -> { is VerificationAction.SASDoNotMatchAction -> {
(session.cryptoService().verificationService() handleSASDoNotMatchAction(action)
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
as? SasVerificationTransaction)
?.shortCodeDoesNotMatch()
} }
is VerificationAction.GotItConclusion -> { is VerificationAction.GotItConclusion -> {
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
@ -365,6 +284,85 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}.exhaustive }.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) { private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { 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) { override fun transactionCreated(tx: VerificationTransaction) {
transactionUpdated(tx) transactionUpdated(tx)
} }
override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
if (state.selfVerificationMode && state.transactionId == null) { handleTransactionUpdate(state, tx)
// 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 verificationRequestCreated(pr: PendingVerificationRequest) { override fun verificationRequestCreated(pr: PendingVerificationRequest) {
@ -514,12 +518,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
if (!pr.isReady) { if (!pr.isReady) {
// auto ready in this case, as we are waiting // 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? // TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService() viewModelScope.launch {
.readyPendingVerification( session.cryptoService().verificationService()
supportedVerificationMethodsProvider.provide(), .readyPendingVerification(
pr.otherUserId, supportedVerificationMethodsProvider.provide(),
pr.transactionId ?: "" pr.otherUserId,
) pr.transactionId ?: ""
)
}
} }
// Use this one! // 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.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -220,30 +219,27 @@ class HomeActivityViewModel @AssistedInject constructor(
// Try to initialize cross signing in background if possible // Try to initialize cross signing in background if possible
Timber.d("Initialize cross signing...") Timber.d("Initialize cross signing...")
try { try {
awaitCallback<Unit> { session.cryptoService().crossSigningService().initializeCrossSigning(
session.cryptoService().crossSigningService().initializeCrossSigning( object : UserInteractiveAuthInterceptor {
object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
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
// We missed server grace period or it's not setup, see if we remember locally password if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && errCode == null &&
errCode == null && reAuthHelper.data != null) {
reAuthHelper.data != null) { promise.resume(
promise.resume( UserPasswordAuth(
UserPasswordAuth( session = flowResponse.session,
session = flowResponse.session, user = session.myUserId,
user = session.myUserId, password = reAuthHelper.data
password = reAuthHelper.data )
) )
) } else {
} else { promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
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) { } catch (failure: Throwable) {
Timber.e(failure, "Failed to initialize cross signing") Timber.e(failure, "Failed to initialize cross signing")
} }

View File

@ -80,6 +80,7 @@ class HomeDrawerFragment @Inject constructor(
} }
// Sign out // Sign out
views.homeDrawerHeaderSignoutView.debouncedClicks { views.homeDrawerHeaderSignoutView.debouncedClicks {
signout()
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
SignOutUiWorker(requireActivity()).perform() SignOutUiWorker(requireActivity()).perform()
} }
@ -118,4 +119,7 @@ class HomeDrawerFragment @Inject constructor(
navigator.openDebug(requireActivity()) 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 im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample 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.extensions.orFalse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@ -58,7 +59,7 @@ data class DeviceDetectionInfo(
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState, class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
session: Session, session: Session,
private val vectorPreferences: VectorPreferences) : private val vectorPreferences: VectorPreferences) :
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) { VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
sealed class Action : VectorViewModelAction { sealed class Action : VectorViewModelAction {
data class IgnoreDevice(val deviceIds: List<String>) : Action() data class IgnoreDevice(val deviceIds: List<String>) : Action()
@ -75,12 +76,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
init { 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( ignoredDeviceList.addAll(
vectorPreferences.getUnknownDeviceDismissedList().also { vectorPreferences.getUnknownDeviceDismissedList().also {
Timber.v("## Detector - Remembered ignored list $it") Timber.v("## Detector - Remembered ignored list $it")
@ -90,10 +85,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
combine( combine(
session.flow().liveUserCryptoDevices(session.myUserId), session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo(), session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningPrivateKeys() session.flow().liveCrossSigningPrivateKeys(),
) { cryptoList, infoList, pInfo -> session.firstTimeDeviceSeen(),
) { cryptoList, infoList, pInfo, firstTimeDeviceSeen ->
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") // Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
Timber.v("## Detector - Current Session first time seen $firstTimeDeviceSeen")
infoList infoList
.filter { info -> .filter { info ->
// filter verified session, by checking the crypto device 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 val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
DeviceDetectionInfo( DeviceDetectionInfo(
deviceInfo, 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 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) .sample(5_000)
.onEach { .onEach {
// If we have a new crypto device change, we might want to trigger refresh of device info // 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) .launchIn(viewModelScope)
// trigger a refresh of lastSeen / last Ip // trigger a refresh of lastSeen / last Ip
session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) viewModelScope.launch {
session.cryptoService().fetchDevicesList()
}
} }
override fun handle(action: Action) { override fun handle(action: Action) {
@ -154,4 +153,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList) vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
super.onCleared() 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) { private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}") Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
if (session.cryptoService().verificationService().readyPendingVerificationInDMs( viewModelScope.launch {
supportedVerificationMethodsProvider.provide(), if (session.cryptoService().verificationService().readyPendingVerification(
action.otherUserId, supportedVerificationMethodsProvider.provide(),
room.roomId, action.otherUserId,
action.transactionId)) { action.transactionId)) {
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
} else { } else {
// TODO // TODO
}
} }
} }
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) { private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
session.cryptoService().verificationService().declineVerificationRequestInDMs( viewModelScope.launch {
action.otherUserId, session.cryptoService().verificationService().cancelVerificationRequest(
action.transactionId, action.otherUserId,
room.roomId) action.transactionId)
}
} }
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) { private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {

View File

@ -164,11 +164,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions -> onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
val nonNullTimelineEvent = timelineEvent() ?: return@onEach val nonNullTimelineEvent = timelineEvent() ?: return@onEach
eventIdFlow.tryEmit(nonNullTimelineEvent.eventId) eventIdFlow.tryEmit(nonNullTimelineEvent.eventId)
val events = actionsForEvent(nonNullTimelineEvent, permissions)
setState { setState {
copy( copy(
eventId = nonNullTimelineEvent.eventId, eventId = nonNullTimelineEvent.eventId,
messageBody = computeMessageBody(nonNullTimelineEvent), 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 messageContent = timelineEvent.getLastMessageContent()
val msgType = messageContent?.msgType val msgType = messageContent?.msgType
@ -317,7 +318,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
// TODO sent by me or sufficient power level // 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, actionPermissions: ActionPermissions,
messageContent: MessageContent?, messageContent: MessageContent?,
msgType: String?) { msgType: String?) {
@ -400,7 +401,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
) { ) {
add(EventSharedAction.UseKeyBackup) add(EventSharedAction.UseKeyBackup)
} }
if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1 || if (session.cryptoService().getCryptoDeviceInfoList(session.myUserId).size > 1 ||
timelineEvent.senderInfo.userId != session.myUserId) { timelineEvent.senderInfo.userId != session.myUserId) {
add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) 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.ReferencesInfoData
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory 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.crypto.VerificationState
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session 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 { private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration = runBlocking {
return if ( if (event.root.sendState != SendState.SYNCED) {
event.root.sendState == SendState.SYNCED && return@runBlocking E2EDecoration.NONE
roomSummary?.isEncrypted.orFalse() && }
// is user verified if (!roomSummary?.isEncrypted.orFalse()) {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true) { return@runBlocking E2EDecoration.NONE
val ts = roomSummary?.encryptionEventTs ?: 0 }
val eventTs = event.root.originServerTs ?: 0 val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
if (event.isEncrypted()) { if (!isUserVerified) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info) return@runBlocking E2EDecoration.NONE
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) { }
E2EDecoration.NONE val ts = roomSummary?.encryptionEventTs ?: 0
} else { val eventTs = event.root.originServerTs ?: 0
val sendingDevice = event.root.content return@runBlocking if (event.isEncrypted()) {
.toModel<EncryptedEventContent>() // Do not decorate failed to decrypt, or redaction (we lost sender device info)
?.deviceId if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
?.let { deviceId -> E2EDecoration.NONE
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
}
}
}
} else { } else {
if (event.root.isStateEvent()) { val sendingDevice = event.root.content
// Do not warn for state event, they are always in clear .toModel<EncryptedEventContent>()
E2EDecoration.NONE ?.deviceId
} else { ?.let { deviceId ->
// If event is in clear after the room enabled encryption we should warn session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId)
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE }
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 { } 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.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.features.widgets.WidgetArgsBuilder
import im.vector.app.space 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.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom 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 widgetArgsBuilder: WidgetArgsBuilder,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val features: VectorFeatures private val features: VectorFeatures,
private val coroutineScope: CoroutineScope
) : Navigator { ) : Navigator {
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { 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) { override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
val session = sessionHolder.getSafeActiveSession() ?: return coroutineScope.launch {
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId) val session = sessionHolder.getSafeActiveSession() ?: return@launch
?: return val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
if (tx is SasVerificationTransaction && tx.isIncoming) { ?: return@launch
tx.acceptVerification() if (tx is SasVerificationTransaction && tx.isIncoming) {
} tx.acceptVerification()
}
if (context is AppCompatActivity) { if (context is AppCompatActivity) {
VerificationBottomSheet.withArgs( VerificationBottomSheet.withArgs(
roomId = null, roomId = null,
otherUserId = otherUserId, otherUserId = otherUserId,
transactionId = sasTransactionId transactionId = sasTransactionId
).show(context.supportFragmentManager, "REQPOP") ).show(context.supportFragmentManager, "REQPOP")
}
} }
} }
override fun requestSessionVerification(context: Context, otherSessionId: String) { override fun requestSessionVerification(context: Context, otherSessionId: String) {
val session = sessionHolder.getSafeActiveSession() ?: return coroutineScope.launch {
val pr = session.cryptoService().verificationService().requestKeyVerification( val session = sessionHolder.getSafeActiveSession() ?: return@launch
supportedVerificationMethodsProvider.provide(), val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
session.myUserId, supportedVerificationMethodsProvider.provide()
listOf(otherSessionId) )
) if (context is AppCompatActivity) {
if (context is AppCompatActivity) { VerificationBottomSheet.withArgs(
VerificationBottomSheet.withArgs( roomId = null,
roomId = null, otherUserId = session.myUserId,
otherUserId = session.myUserId, transactionId = pr.transactionId
transactionId = pr.transactionId ).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) }
} }
} }
override fun requestSelfSessionVerification(context: Context) { override fun requestSelfSessionVerification(context: Context) {
val session = sessionHolder.getSafeActiveSession() ?: return coroutineScope.launch {
val otherSessions = session.cryptoService() val session = sessionHolder.getSafeActiveSession() ?: return@launch
.getCryptoDeviceInfo(session.myUserId) val otherSessions = session.cryptoService()
.filter { it.deviceId != session.sessionParams.deviceId } .getCryptoDeviceInfoList(session.myUserId)
.map { it.deviceId } .filter { it.deviceId != session.sessionParams.deviceId }
if (context is AppCompatActivity) { .map { it.deviceId }
if (otherSessions.isNotEmpty()) { if (context is AppCompatActivity) {
val pr = session.cryptoService().verificationService().requestKeyVerification( if (otherSessions.isNotEmpty()) {
supportedVerificationMethodsProvider.provide(), val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
session.myUserId, supportedVerificationMethodsProvider.provide())
otherSessions) VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId) .show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) } else {
} else { VerificationBottomSheet.forSelfVerification(session)
VerificationBottomSheet.forSelfVerification(session) .show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) }
} }
} }
} }
@ -404,13 +409,15 @@ class DefaultNavigator @Inject constructor(
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) { 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 // if cross signing is enabled and trusted or not set up at all we should propose full 4S
sessionHolder.getSafeActiveSession()?.let { session -> sessionHolder.getSafeActiveSession()?.let { session ->
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null || coroutineScope.launch {
session.cryptoService().crossSigningService().canCrossSign()) { if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
(context as? AppCompatActivity)?.let { session.cryptoService().crossSigningService().canCrossSign()) {
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL) (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) { if (root.isEncrypted() && root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId? // TODO use a global event decryptor? attache to session and that listen to new sessionId?
// for now decrypt sync // 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.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel 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.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo 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.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
@ -124,8 +124,13 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) { private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
if (!initialState.allowDeviceAction) return if (!initialState.allowDeviceAction) return
session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, initialState.userId, action.deviceId, null)?.let { txID -> viewModelScope.launch {
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID)) 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 package im.vector.app.features.roomprofile.members
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@ -94,8 +93,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
if (room.isEncrypted()) { if (room.isEncrypted()) {
room.flow().liveRoomMembers(roomMemberQueryParams) room.flow().liveRoomMembers(roomMemberQueryParams)
.flatMapLatest { membersSummary -> .flatMapLatest { membersSummary ->
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) session.cryptoService().getLiveCryptoDeviceInfoList(membersSummary.map { it.userId })
.asFlow()
.catch { Timber.e(it) } .catch { Timber.e(it) }
.map { deviceList -> .map { deviceList ->
// If any key change, emit the userIds list // 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.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.gujun.android.span.span 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.extensions.getFingerprintHumanReadable
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified 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.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import javax.inject.Inject import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor( class VectorSettingsSecurityPrivacyFragment @Inject constructor(
@ -317,31 +315,32 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// Todo this should be refactored and use same state as 4S section // Todo this should be refactored and use same state as 4S section
private fun refreshXSigningStatus() { private fun refreshXSigningStatus() {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys() lifecycleScope.launchWhenResumed {
val xSigningIsEnableInAccount = crossSigningKeys != null val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
when { when {
xSigningKeyCanSign -> { xSigningKeyCanSign -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted) mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete) mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
} }
xSigningKeysAreTrusted -> { xSigningKeysAreTrusted -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom) mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted) mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
} }
xSigningIsEnableInAccount -> { xSigningIsEnableInAccount -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black) mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted) mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
} }
else -> { else -> {
mCrossSigningStatePreference.setIcon(android.R.color.transparent) mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled) mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
}
} }
mCrossSigningStatePreference.isVisible = true
} }
mCrossSigningStatePreference.isVisible = true
} }
private val saveMegolmStartForActivityResult = registerStartForActivityResult { private val saveMegolmStartForActivityResult = registerStartForActivityResult {
@ -523,7 +522,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
/** /**
* Build the cryptography preference section. * Build the cryptography preference section.
*/ */
private fun refreshCryptographyPreference(devices: List<DeviceInfo>) { private suspend fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
showDeviceListPref.isEnabled = devices.isNotEmpty() showDeviceListPref.isEnabled = devices.isNotEmpty()
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) 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) // crypto section: device key (fingerprint)
val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId) val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId)
val fingerprint = deviceInfo?.fingerprint() val fingerprint = deviceInfo?.fingerprint()
if (fingerprint?.isNotEmpty() == true) { if (fingerprint?.isNotEmpty() == true) {
@ -579,28 +578,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// ============================================================================================================== // ==============================================================================================================
private fun refreshMyDevice() { private fun refreshMyDevice() {
session.cryptoService().getUserDevices(session.myUserId).map { viewLifecycleOwner.lifecycleScope.launchWhenResumed {
DeviceInfo( session.cryptoService().getUserDevices(session.myUserId).map {
userId = session.myUserId, DeviceInfo(
deviceId = it.deviceId, userId = session.myUserId,
displayName = it.displayName() deviceId = it.deviceId,
) displayName = it.displayName()
}.let { )
refreshCryptographyPreference(it) }.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 im.vector.app.features.login.ReAuthHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor 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.fromBase64
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified 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.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -55,25 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) { ) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
init { init {
combine( observeCrossSigning()
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
)
}
} }
var uiaContinuation: Continuation<UIABaseAuth>? = null var uiaContinuation: Continuation<UIABaseAuth>? = null
@ -90,29 +72,27 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null)) _viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
awaitCallback<Unit> { session.cryptoService().crossSigningService().initializeCrossSigning(
session.cryptoService().crossSigningService().initializeCrossSigning( object : UserInteractiveAuthInterceptor {
object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse,
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?,
errCode: String?, promise: Continuation<UIABaseAuth>) {
promise: Continuation<UIABaseAuth>) { Timber.d("## UIA : initializeCrossSigning UIA")
Timber.d("## UIA : initializeCrossSigning UIA") if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
reAuthHelper.data != null && errCode == null) { UserPasswordAuth(
UserPasswordAuth( session = null,
session = null, user = session.myUserId,
user = session.myUserId, password = reAuthHelper.data
password = reAuthHelper.data ).let { promise.resume(it) }
).let { promise.resume(it) } } else {
} else { Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity") _viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode)) pendingAuth = DefaultBaseAuth(session = flowResponse.session)
pendingAuth = DefaultBaseAuth(session = flowResponse.session) uiaContinuation = promise
uiaContinuation = promise
}
} }
}, it) }
} })
} catch (failure: Throwable) { } catch (failure: Throwable) {
handleInitializeXSigningError(failure) handleInitializeXSigningError(failure)
} finally { } finally {
@ -149,6 +129,28 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
}.exhaustive }.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) { private fun handleInitializeXSigningError(failure: Throwable) {
Timber.e(failure, "## CrossSigning - Failed to initialize cross signing") Timber.e(failure, "## CrossSigning - Failed to initialize cross signing")
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.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 package im.vector.app.features.settings.devices
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory 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.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
@ -43,14 +43,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
by hiltMavericksViewModelFactory() by hiltMavericksViewModelFactory()
init { init {
initState()
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
)
}
session.flow().liveCrossSigningInfo(session.myUserId) session.flow().liveCrossSigningInfo(session.myUserId)
.execute { .execute {
copy( copy(
@ -78,10 +71,6 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
) )
} }
setState {
copy(deviceInfo = Loading())
}
session.flow().liveMyDevicesInfo() session.flow().liveMyDevicesInfo()
.map { devices -> .map { devices ->
devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId) 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) { 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.auth.ReAuthActivity
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import im.vector.lib.core.utils.flow.throttleFirst import im.vector.lib.core.utils.flow.throttleFirst
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -43,8 +42,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch 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.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.UserPasswordAuth
@ -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.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session 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.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction 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.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.flow.flow 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.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo 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.DefaultBaseAuth
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
@ -106,15 +100,7 @@ class DevicesViewModel @AssistedInject constructor(
private val refreshSource = PublishDataSource<Unit>() private val refreshSource = PublishDataSource<Unit>()
init { init {
initState()
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
myDeviceId = session.sessionParams.deviceId ?: ""
)
}
combine( combine(
session.flow().liveUserCryptoDevices(session.myUserId), session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo() session.flow().liveMyDevicesInfo()
@ -155,7 +141,7 @@ class DevicesViewModel @AssistedInject constructor(
.sample(5_000) .sample(5_000)
.onEach { .onEach {
// If we have a new crypto device change, we might want to trigger refresh of device info // 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) .launchIn(viewModelScope)
@ -168,7 +154,7 @@ class DevicesViewModel @AssistedInject constructor(
refreshSource.stream().throttleFirst(4_000) refreshSource.stream().throttleFirst(4_000)
.onEach { .onEach {
session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) session.cryptoService().fetchDevicesList()
session.cryptoService().downloadKeys(listOf(session.myUserId), true) session.cryptoService().downloadKeys(listOf(session.myUserId), true)
} }
.launchIn(viewModelScope) .launchIn(viewModelScope)
@ -176,6 +162,21 @@ class DevicesViewModel @AssistedInject constructor(
queryRefreshDevicesList() 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() { override fun onCleared() {
session.cryptoService().verificationService().removeListener(this) session.cryptoService().verificationService().removeListener(this)
super.onCleared() super.onCleared()
@ -240,13 +241,15 @@ class DevicesViewModel @AssistedInject constructor(
} }
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) { private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
val txID = session.cryptoService() viewModelScope.launch {
.verificationService() val txID = session.cryptoService()
.beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null) .verificationService()
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice( .beginDeviceVerification(session.myUserId, action.deviceId)
session.myUserId, _viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
txID session.myUserId,
)) txID
))
}
} }
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state -> private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
@ -268,8 +271,7 @@ class DevicesViewModel @AssistedInject constructor(
} }
} else { } else {
// legacy // legacy
session.cryptoService().setDeviceVerification( session.cryptoService().verificationService().markedLocallyAsManuallyVerified(
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
action.cryptoDeviceInfo.userId, action.cryptoDeviceInfo.userId,
action.cryptoDeviceInfo.deviceId) action.cryptoDeviceInfo.deviceId)
} }
@ -288,27 +290,21 @@ class DevicesViewModel @AssistedInject constructor(
} }
private fun handleRename(action: DevicesAction.Rename) { private fun handleRename(action: DevicesAction.Rename) {
session.cryptoService().setDeviceName(action.deviceId, action.newName, object : MatrixCallback<Unit> { viewModelScope.launch {
override fun onSuccess(data: Unit) { try {
session.cryptoService().setDeviceName(action.deviceId, action.newName)
setState { setState {
copy( copy(request = Success(Unit))
request = Success(data)
)
} }
// force settings update // force settings update
queryRefreshDevicesList() queryRefreshDevicesList()
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
setState { setState {
copy( copy(request = Fail(failure))
request = Fail(failure)
)
} }
_viewEvents.post(DevicesViewEvents.Failure(failure)) _viewEvents.post(DevicesViewEvents.Failure(failure))
} }
}) }
} }
/** /**
@ -323,39 +319,32 @@ class DevicesViewModel @AssistedInject constructor(
) )
} }
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch {
try { try {
awaitCallback<Unit> { session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { Timber.d("## UIA : deleteDevice UIA")
Timber.d("## UIA : deleteDevice UIA") if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) { UserPasswordAuth(
UserPasswordAuth( session = null,
session = null, user = session.myUserId,
user = session.myUserId, password = reAuthHelper.data
password = reAuthHelper.data ).let { promise.resume(it) }
).let { promise.resume(it) } } else {
} else { Timber.d("## UIA : deleteDevice UIA > start reauth activity")
Timber.d("## UIA : deleteDevice UIA > start reauth activity") _viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode)) pendingAuth = DefaultBaseAuth(session = flowResponse.session)
pendingAuth = DefaultBaseAuth(session = flowResponse.session) uiaContinuation = promise
uiaContinuation = promise
}
} }
}, it) }
} })
setState { setState {
copy( copy(request = Success(Unit))
request = Success(Unit)
)
} }
// force settings update
queryRefreshDevicesList() queryRefreshDevicesList()
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState { setState {
copy( copy(request = Fail(failure))
request = Fail(failure)
)
} }
if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) { if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
_viewEvents.post(DevicesViewEvents.Failure(Exception(stringProvider.getString(R.string.authentication_error)))) _viewEvents.post(DevicesViewEvents.Failure(Exception(stringProvider.getString(R.string.authentication_error))))

View File

@ -95,7 +95,7 @@ class SoftLogoutActivity : LoginActivity() {
MainActivity.restartApp(this, MainActivityArgs()) MainActivity.restartApp(this, MainActivityArgs())
} }
views.loginLoading.isVisible = softLogoutViewState.isLoading() views.loginLoading.isVisible = softLogoutViewState.isLoading
} }
companion object { 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.loginTextItem
import im.vector.app.features.signout.soft.epoxy.loginTitleItem import im.vector.app.features.signout.soft.epoxy.loginTitleItem
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject import javax.inject.Inject
class SoftLogoutController @Inject constructor( class SoftLogoutController @Inject constructor(
@ -52,6 +53,7 @@ class SoftLogoutController @Inject constructor(
override fun buildModels() { override fun buildModels() {
val safeViewState = viewState ?: return val safeViewState = viewState ?: return
if (safeViewState.hasUnsavedKeys is Incomplete) return
buildHeader(safeViewState) buildHeader(safeViewState)
buildForm(safeViewState) buildForm(safeViewState)
@ -78,7 +80,7 @@ class SoftLogoutController @Inject constructor(
state.userDisplayName, state.userDisplayName,
state.userId)) state.userId))
} }
if (state.hasUnsavedKeys) { if (state.hasUnsavedKeys().orFalse()) {
loginTextItem { loginTextItem {
id("signText2") id("signText2")
text(host.stringProvider.getString(R.string.soft_logout_signin_e2e_warning_notice)) 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.LoginAction
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.LoginViewEvents import im.vector.app.features.login.LoginViewEvents
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -118,7 +119,7 @@ class SoftLogoutFragment @Inject constructor(
withState(softLogoutViewModel) { state -> withState(softLogoutViewModel) { state ->
cleanupUi() cleanupUi()
val messageResId = if (state.hasUnsavedKeys) { val messageResId = if (state.hasUnsavedKeys().orFalse()) {
R.string.soft_logout_clear_data_dialog_e2e_warning_content R.string.soft_logout_clear_data_dialog_e2e_warning_content
} else { } else {
R.string.soft_logout_clear_data_dialog_content R.string.soft_logout_clear_data_dialog_content

View File

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

View File

@ -30,13 +30,12 @@ data class SoftLogoutViewState(
val userId: String, val userId: String,
val deviceId: String, val deviceId: String,
val userDisplayName: String, val userDisplayName: String,
val hasUnsavedKeys: Boolean, val hasUnsavedKeys: Async<Boolean> = Uninitialized,
val enteredPassword: String = "" val enteredPassword: String = ""
) : MavericksState { ) : MavericksState {
fun isLoading(): Boolean { val isLoading: Boolean =
return asyncLoginAction is Loading || asyncLoginAction is Loading ||
// Keep loading when it is success because of the delay to switch to the next Activity // Keep loading when it is success because of the delay to switch to the next Activity
asyncLoginAction is Success 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 * Safe way to get the number of keys to backup
*/ */
fun getNumberOfKeysToBackup(): Int { private suspend fun getNumberOfKeysToBackup(): Int {
return session.cryptoService().inboundGroupSessionsCount(false) return session.cryptoService().inboundGroupSessionsCount(false)
} }
/** /**
* Safe way to tell if there are more keys on the server * 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() 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 package im.vector.app.features.workers.signout
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cannotLogoutSafely import im.vector.app.core.extensions.cannotLogoutSafely
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs 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) { class SignOutUiWorker(private val activity: FragmentActivity) {
fun perform() { fun perform() {
val session = activity.singletonEntryPoint().activeSessionHolder().getSafeActiveSession() ?: return val session = activity.singletonEntryPoint().activeSessionHolder().getSafeActiveSession() ?: return
activity.lifecycleScope.perform(session)
}
private fun CoroutineScope.perform(session: Session) = launch {
if (session.cannotLogoutSafely()) { 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 // 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() 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() = 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() cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
) )
} }