mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-28 22:18:46 +03:00
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:
commit
559404f953
84 changed files with 2127 additions and 2123 deletions
|
@ -125,21 +125,21 @@ class FlowSession(private val session: Session) {
|
|||
}
|
||||
|
||||
fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> {
|
||||
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow()
|
||||
return session.cryptoService().getLiveCryptoDeviceInfoList(userId)
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
session.cryptoService().getCryptoDeviceInfo(userId)
|
||||
session.cryptoService().getCryptoDeviceInfoList(userId)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
||||
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow()
|
||||
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId)
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
|
||||
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow()
|
||||
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys()
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
|
||||
}
|
||||
|
|
|
@ -110,6 +110,9 @@ dependencies {
|
|||
implementation libs.jetbrains.coroutinesAndroid
|
||||
|
||||
implementation 'org.matrix.rustcomponents:crypto-android:0.1.1-SNAPSHOT'
|
||||
//implementation files('libs/crypto-android-release.aar')
|
||||
|
||||
// implementation(name: 'crypto-android-release', ext: 'aar')
|
||||
implementation 'net.java.dev.jna:jna:5.10.0@aar'
|
||||
|
||||
implementation libs.androidx.appCompat
|
||||
|
|
BIN
matrix-sdk-android/libs/crypto-android-release.aar
Normal file
BIN
matrix-sdk-android/libs/crypto-android-release.aar
Normal file
Binary file not shown.
|
@ -378,7 +378,7 @@ class CommonTestHelper(context: Context) {
|
|||
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: suspend (() -> Boolean)) {
|
||||
while (true) {
|
||||
delay(1000)
|
||||
if (condition()) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.common
|
|||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
|
@ -29,8 +30,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
|||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -275,7 +275,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
}
|
||||
|
||||
fun initializeCrossSigning(session: Session) {
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.runBlockingTest {
|
||||
session.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -288,7 +288,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,24 +300,24 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
val aliceVerificationService = alice.cryptoService().verificationService()
|
||||
val bobVerificationService = bob.cryptoService().verificationService()
|
||||
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID,
|
||||
roomId,
|
||||
bob.myUserId,
|
||||
bob.sessionParams.credentials.deviceId!!)
|
||||
runBlocking {
|
||||
aliceVerificationService.beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
roomId,
|
||||
bob.myUserId,)
|
||||
}
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
||||
var alicePovTx: SasVerificationTransaction? = null
|
||||
var bobPovTx: SasVerificationTransaction? = null
|
||||
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
bobPovTx?.acceptVerification()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -327,18 +327,18 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.state}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
bobPovTx?.acceptVerification()
|
||||
}
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
|
@ -346,8 +346,10 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
|
||||
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
||||
|
||||
bobPovTx!!.userHasVerifiedShortCode()
|
||||
alicePovTx!!.userHasVerifiedShortCode()
|
||||
runBlocking {
|
||||
bobPovTx!!.userHasVerifiedShortCode()
|
||||
alicePovTx!!.userHasVerifiedShortCode()
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
|
|
|
@ -60,8 +60,8 @@ class PreShareKeysTest : InstrumentedTest {
|
|||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||
|
||||
// Force presharing of new outbound key
|
||||
testHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
|
|
|
@ -144,9 +144,12 @@ class UnwedgingTest : InstrumentedTest {
|
|||
// - Store the olm session between A&B devices
|
||||
// Let us pickle our session with bob here so we can later unpickle it
|
||||
// and wedge our session.
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
var myDevice = testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().getMyCryptoDevice()
|
||||
}
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(myDevice.identityKey()!!)
|
||||
sessionIdsForBob!!.size shouldBe 1
|
||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), myDevice.identityKey()!!)!!
|
||||
|
||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||
|
||||
|
@ -174,7 +177,10 @@ class UnwedgingTest : InstrumentedTest {
|
|||
// Let us wedge the session now. Set crypto state like after the first message
|
||||
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
|
||||
|
||||
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
myDevice = testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().getMyCryptoDevice()
|
||||
}
|
||||
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), myDevice.identityKey()!!)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
// Force new session, and key share
|
||||
|
@ -207,7 +213,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -220,7 +226,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
|
|||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
@ -53,7 +54,7 @@ class XSigningTest : InstrumentedTest {
|
|||
fun test_InitializeAndStoreKeys() {
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
|
@ -65,10 +66,12 @@ class XSigningTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
|
||||
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
val myCrossSigningKeys = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
}
|
||||
val masterPubKey = myCrossSigningKeys?.masterKey()
|
||||
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
||||
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
||||
|
@ -78,7 +81,10 @@ class XSigningTest : InstrumentedTest {
|
|||
|
||||
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
||||
|
||||
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
||||
val userTrustResult = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId)
|
||||
}
|
||||
assertTrue("Signing Keys should be trusted", userTrustResult.isVerified())
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
@ -99,29 +105,37 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
}
|
||||
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it) }
|
||||
|
||||
// Check that alice can see bob keys
|
||||
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||
val bobKeysFromAlicePOV = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||
}
|
||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
||||
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||
val myKeys = testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
}
|
||||
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||
|
||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||
|
||||
|
@ -145,25 +159,33 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it) }
|
||||
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it) }
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
})
|
||||
}
|
||||
testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Check that alice can see bob keys
|
||||
val bobUserId = bobSession.myUserId
|
||||
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||
val bobKeysFromAlicePOV = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||
}
|
||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||
|
||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().trustUser(bobUserId)
|
||||
}
|
||||
|
||||
// Now bobs logs in on a new device and verifies it
|
||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||
|
@ -180,7 +202,9 @@ class XSigningTest : InstrumentedTest {
|
|||
fail("Bob should see the new device")
|
||||
}
|
||||
|
||||
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||
val bobSecondDevicePOVFirstDevice = runBlocking {
|
||||
bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||
}
|
||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||
|
||||
// Manually mark it as trusted from first session
|
||||
|
@ -198,7 +222,9 @@ class XSigningTest : InstrumentedTest {
|
|||
fail("Alice should see the new device")
|
||||
}
|
||||
|
||||
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||
val result = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||
}
|
||||
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
|
|
|
@ -23,6 +23,7 @@ import junit.framework.TestCase.assertEquals
|
|||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.fail
|
||||
import kotlinx.coroutines.delay
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
|
@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
|||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
|
@ -50,7 +50,6 @@ import org.matrix.android.sdk.common.SessionTestParams
|
|||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
|
||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
@ -93,7 +92,9 @@ class KeyShareTests : InstrumentedTest {
|
|||
assert(receivedEvent!!.isEncrypted())
|
||||
|
||||
try {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
fail("should fail")
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
@ -106,7 +107,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
var outGoingRequestId: String? = null
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.filter { req ->
|
||||
|
@ -148,14 +149,17 @@ class KeyShareTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
try {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
fail("should fail")
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
||||
// Mark the device as trusted
|
||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||
aliceSession2.sessionParams.deviceId ?: "")
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession2.sessionParams.deviceId ?: "")
|
||||
}
|
||||
|
||||
// Re request
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
@ -185,7 +189,9 @@ class KeyShareTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
try {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
fail("should have been able to decrypt")
|
||||
}
|
||||
|
@ -199,7 +205,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
fun test_ShareSSSSSecret() {
|
||||
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
commonTestHelper.doSync<Unit> {
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession1.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -211,7 +217,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
|
||||
// Also bootstrap keybackup on first session
|
||||
|
@ -242,27 +248,30 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
aliceVerificationService1.addListener(object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx !is SasVerificationTransaction) return
|
||||
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.OnStarted) {
|
||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||
when (tx.state) {
|
||||
VerificationTxState.OnStarted -> commonTestHelper.runBlockingTest {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
VerificationTxState.ShortCodeReady -> commonTestHelper.runBlockingTest {
|
||||
session1ShortCode = tx.getDecimalCodeRepresentation()
|
||||
Thread.sleep(500)
|
||||
delay(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
aliceVerificationService2.addListener(object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx !is SasVerificationTransaction) return
|
||||
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
when (tx.state) {
|
||||
VerificationTxState.ShortCodeReady -> commonTestHelper.runBlockingTest {
|
||||
session2ShortCode = tx.getDecimalCodeRepresentation()
|
||||
Thread.sleep(500)
|
||||
delay(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
|
@ -270,12 +279,13 @@ class KeyShareTests : InstrumentedTest {
|
|||
})
|
||||
|
||||
val txId = "m.testVerif12"
|
||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||
?: "", txId)
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, txId)
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||
aliceSession1.cryptoService().getCryptoDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,7 +322,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
fun test_ImproperKeyShareBug() {
|
||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
commonTestHelper.doSync<Unit> {
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -325,7 +335,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
|
||||
// Create an encrypted room and send a couple of messages
|
||||
|
@ -346,7 +356,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
// Create bob session
|
||||
|
||||
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
||||
commonTestHelper.doSync<Unit> {
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -359,7 +369,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
})
|
||||
}
|
||||
|
||||
// Let alice invite bob
|
||||
|
@ -380,7 +390,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
val roomRoomBobPov = aliceSession.getRoom(roomId)
|
||||
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
|
||||
|
||||
var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
|
||||
var dRes =
|
||||
commonTestHelper.runBlockingTest {
|
||||
tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
|
||||
}
|
||||
|
||||
assert(dRes == null)
|
||||
|
||||
|
@ -391,7 +404,9 @@ class KeyShareTests : InstrumentedTest {
|
|||
Thread.sleep(3_000)
|
||||
|
||||
// With the bug the first session would have improperly reshare that key :/
|
||||
dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
|
||||
dRes = commonTestHelper.runBlockingTest {
|
||||
tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
|
||||
}
|
||||
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
|
||||
assert(dRes?.clearEvent == null)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.gossiping
|
|||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
|
@ -92,7 +93,9 @@ class WithHeldTests : InstrumentedTest {
|
|||
// Bob should not be able to decrypt because the keys is withheld
|
||||
try {
|
||||
// .. might need to wait a bit for stability?
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
runBlocking {
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
Assert.fail("This session should not be able to decrypt")
|
||||
} catch (failure: Throwable) {
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
|
@ -117,7 +120,9 @@ class WithHeldTests : InstrumentedTest {
|
|||
// Previous message should still be undecryptable (partially withheld session)
|
||||
try {
|
||||
// .. might need to wait a bit for stability?
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
runBlocking {
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
Assert.fail("This session should not be able to decrypt")
|
||||
} catch (failure: Throwable) {
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
|
@ -164,7 +169,9 @@ class WithHeldTests : InstrumentedTest {
|
|||
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
||||
try {
|
||||
// .. might need to wait a bit for stability?
|
||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
||||
runBlocking {
|
||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
||||
}
|
||||
Assert.fail("This session should not be able to decrypt")
|
||||
} catch (failure: Throwable) {
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
|
@ -222,7 +229,7 @@ class WithHeldTests : InstrumentedTest {
|
|||
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
||||
|
||||
// Trust bob second device from Alice POV
|
||||
mTestHelper.runBlockingTest {
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!)
|
||||
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
@ -38,7 +39,6 @@ import org.matrix.android.sdk.common.CommonTestHelper
|
|||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
|
@ -164,17 +164,22 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||
|
||||
runBlocking {
|
||||
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||
}
|
||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||
|
||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
testHelper.await(latch)
|
||||
|
||||
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
||||
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
||||
val nbOfKeys = runBlocking {
|
||||
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
||||
}
|
||||
val backedUpKeys = runBlocking {
|
||||
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
||||
}
|
||||
|
||||
assertEquals(2, nbOfKeys)
|
||||
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
||||
|
@ -833,9 +838,12 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(1, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
val signature = keysBackupVersionTrust.signatures[0]
|
||||
val device = runBlocking {
|
||||
cryptoTestData.firstSession.cryptoService().getMyCryptoDevice()
|
||||
}
|
||||
assertTrue(signature.valid)
|
||||
assertNotNull(signature.device)
|
||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
||||
assertEquals(device.deviceId, signature.deviceId)
|
||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
@ -1056,7 +1064,9 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertFalse(keysBackup2.isEnabled)
|
||||
|
||||
// - Validate the old device from the new one
|
||||
aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId)
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
|
||||
}
|
||||
|
||||
// -> Backup should automatically enable on the new device
|
||||
val latch4 = CountDownLatch(1)
|
||||
|
|
|
@ -80,7 +80,10 @@ class KeysBackupTestHelper(
|
|||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(2, cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys())
|
||||
val totalNumbersOfBackedUpKeys = testHelper.runBlockingTest {
|
||||
cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||
}
|
||||
Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
|
||||
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
|
@ -88,15 +91,21 @@ class KeysBackupTestHelper(
|
|||
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
// Test check: aliceSession2 has no keys at login
|
||||
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
val inboundGroupSessionCount = testHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().inboundGroupSessionsCount(false)
|
||||
}
|
||||
Assert.assertEquals(0, inboundGroupSessionCount)
|
||||
|
||||
// Wait for backup state to be NotTrusted
|
||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
val totalNumbersOfBackedUpKeysFromNewSession = testHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||
}
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys(),
|
||||
totalNumbersOfBackedUpKeysFromNewSession,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2)
|
||||
}
|
||||
|
@ -182,7 +191,10 @@ class KeysBackupTestHelper(
|
|||
Assert.assertEquals(total, imported)
|
||||
|
||||
// - The new device must have the same count of megolm keys
|
||||
Assert.assertEquals(testData.aliceKeys, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
val inboundGroupSessionCount = testHelper.runBlockingTest {
|
||||
testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)
|
||||
}
|
||||
Assert.assertEquals(testData.aliceKeys, inboundGroupSessionCount)
|
||||
|
||||
// - Alice must have the same keys on both devices
|
||||
//
|
||||
|
|
|
@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.crypto.verification
|
|||
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.FixMethodOrder
|
||||
|
@ -32,9 +32,7 @@ import org.junit.runners.MethodSorters
|
|||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
|
@ -49,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -75,10 +74,13 @@ class SASTest : InstrumentedTest {
|
|||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
|
||||
bobSession.myUserId,
|
||||
bobSession.cryptoService().getMyDevice().deviceId,
|
||||
null)
|
||||
val bobDevice = testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().getMyCryptoDevice()
|
||||
}
|
||||
val txID = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), forceDownload = true)
|
||||
aliceVerificationService.beginDeviceVerification(bobSession.myUserId, bobDevice.deviceId)
|
||||
}
|
||||
assertNotNull("Alice should have a started transaction", txID)
|
||||
|
||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
|
@ -90,16 +92,13 @@ class SASTest : InstrumentedTest {
|
|||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
|
||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
||||
assertTrue(bobKeyTx is SasVerificationTransaction)
|
||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
||||
assertTrue(aliceKeyTx is SasVerificationTransaction)
|
||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||
|
||||
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
||||
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
||||
|
||||
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
||||
assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
|
||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.state)
|
||||
|
||||
// Let's cancel from alice side
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
|
@ -107,7 +106,7 @@ class SASTest : InstrumentedTest {
|
|||
val bobListener2 = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx.transactionId == txID) {
|
||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
||||
val immutableState = (tx as SasVerificationTransaction).state
|
||||
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
||||
cancelLatch.countDown()
|
||||
}
|
||||
|
@ -116,14 +115,16 @@ class SASTest : InstrumentedTest {
|
|||
}
|
||||
bobVerificationService.addListener(bobListener2)
|
||||
|
||||
aliceSasTx.cancel(CancelCode.User)
|
||||
testHelper.runBlockingTest {
|
||||
aliceKeyTx.cancel(CancelCode.User)
|
||||
}
|
||||
testHelper.await(cancelLatch)
|
||||
|
||||
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
|
||||
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
|
||||
assertTrue("Should be cancelled on alice side", aliceKeyTx.state is VerificationTxState.Cancelled)
|
||||
assertTrue("Should be cancelled on bob side", bobKeyTx.state is VerificationTxState.Cancelled)
|
||||
|
||||
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
|
||||
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
|
||||
val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
|
||||
val bobCancelState = bobKeyTx.state as VerificationTxState.Cancelled
|
||||
|
||||
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
||||
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
||||
|
@ -131,9 +132,6 @@ class SASTest : InstrumentedTest {
|
|||
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
||||
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
||||
|
||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
|
@ -177,12 +175,16 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
||||
val aliceDevice = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||
if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
|
||||
testHelper.runBlockingTest {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,7 +228,9 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
||||
val aliceDevice = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
||||
|
||||
|
@ -267,7 +271,9 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
||||
val aliceDevice = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
||||
|
||||
|
@ -283,12 +289,15 @@ class SASTest : InstrumentedTest {
|
|||
aliceUserID: String?,
|
||||
aliceDevice: String?,
|
||||
tid: String,
|
||||
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
||||
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
||||
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
|
||||
protocols: List<String> = emptyList(),
|
||||
hashes: List<String> = emptyList(),
|
||||
mac: List<String> = emptyList(),
|
||||
codes: List<String> = emptyList()) {
|
||||
val deviceId = runBlocking {
|
||||
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
val startMessage = KeyVerificationStart(
|
||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
||||
fromDevice = deviceId,
|
||||
method = VerificationMethod.SAS.toValue(),
|
||||
transactionId = tid,
|
||||
keyAgreementProtocols = protocols,
|
||||
|
@ -323,16 +332,16 @@ class SASTest : InstrumentedTest {
|
|||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
|
||||
val aliceCreatedLatch = CountDownLatch(2)
|
||||
val aliceCancelledLatch = CountDownLatch(2)
|
||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
||||
val aliceCancelledLatch = CountDownLatch(1)
|
||||
val createdTx = mutableListOf<VerificationTransaction>()
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {
|
||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
||||
createdTx.add(tx)
|
||||
aliceCreatedLatch.countDown()
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
||||
if (tx.state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
||||
aliceCancelledLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
@ -340,10 +349,14 @@ class SASTest : InstrumentedTest {
|
|||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobUserId = bobSession!!.myUserId
|
||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
|
||||
val bobDeviceId = testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), forceDownload = true)
|
||||
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
|
||||
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
|
||||
}
|
||||
testHelper.await(aliceCreatedLatch)
|
||||
testHelper.await(aliceCancelledLatch)
|
||||
|
||||
|
@ -366,17 +379,10 @@ class SASTest : InstrumentedTest {
|
|||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||
val at = tx as SASDefaultVerificationTransaction
|
||||
accepted = at.accepted
|
||||
startReq = at.startReq
|
||||
if (tx.state is VerificationTxState.OnAccepted) {
|
||||
aliceAcceptedLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
@ -385,90 +391,62 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
|
||||
bobVerificationService.removeListener(this)
|
||||
val at = tx as IncomingSasVerificationTransaction
|
||||
at.performAccept()
|
||||
testHelper.runBlockingTest {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
testHelper.await(aliceAcceptedLatch)
|
||||
|
||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||
|
||||
// check that agreement is valid
|
||||
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
||||
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
||||
|
||||
accepted!!.shortAuthenticationStrings.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||
val bobDeviceId = runBlocking {
|
||||
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
testHelper.runBlockingTest {
|
||||
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
}
|
||||
testHelper.await(aliceAcceptedLatch)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_aliceAndBobSASCode() {
|
||||
val supportedMethods = listOf(VerificationMethod.SAS)
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val latch = CountDownLatch(2)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
Timber.v("Alice transactionUpdated: ${tx.state}")
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
Timber.v("Bob transactionUpdated: ${tx.state}")
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
bobSession.cryptoService().verificationService().addListener(bobListener)
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
|
||||
}
|
||||
testHelper.await(latch)
|
||||
val aliceTx = aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
|
||||
val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
testHelper.await(aliceSASLatch)
|
||||
testHelper.await(bobSASLatch)
|
||||
|
||||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||
|
||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
||||
assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
@ -478,7 +456,8 @@ class SASTest : InstrumentedTest {
|
|||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
|
@ -487,21 +466,26 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
Timber.v("RequestUpdated pr=$pr")
|
||||
}
|
||||
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
Timber.v("Alice transactionUpdated: ${tx.state}")
|
||||
if (tx !is SasVerificationTransaction) return
|
||||
when (tx.state) {
|
||||
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
VerificationTxState.Verified -> {
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -511,41 +495,53 @@ class SASTest : InstrumentedTest {
|
|||
val bobListener = object : VerificationService.Listener {
|
||||
var acceptOnce = true
|
||||
var matchOnce = true
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
Timber.v("RequestUpdated: pr=$pr")
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== bobState ${uxState.name}")
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
Timber.v("Bob transactionUpdated: ${tx.state}")
|
||||
if (tx !is SasVerificationTransaction) return
|
||||
when (tx.state) {
|
||||
VerificationTxState.OnStarted -> testHelper.runBlockingTest {
|
||||
if (acceptOnce) {
|
||||
acceptOnce = false
|
||||
tx.performAccept()
|
||||
tx.acceptVerification()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
VerificationTxState.ShortCodeAccepted -> {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
val bobDeviceId = runBlocking {
|
||||
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
testHelper.runBlockingTest {
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
|
||||
}
|
||||
testHelper.await(aliceSASLatch)
|
||||
testHelper.await(bobSASLatch)
|
||||
|
||||
// Assert that devices are verified
|
||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
|
||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
||||
|
||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
|
||||
aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
|
||||
}
|
||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
|
||||
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
|
||||
}
|
||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
|
@ -563,11 +559,13 @@ class SASTest : InstrumentedTest {
|
|||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
bobSession.myUserId,
|
||||
cryptoTestData.roomId
|
||||
)
|
||||
val req = testHelper.runBlockingTest {
|
||||
aliceVerificationService.requestKeyVerificationInDMs(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
bobSession.myUserId,
|
||||
cryptoTestData.roomId
|
||||
)
|
||||
}
|
||||
|
||||
var requestID: String? = null
|
||||
|
||||
|
@ -590,11 +588,13 @@ class SASTest : InstrumentedTest {
|
|||
}
|
||||
}
|
||||
|
||||
bobVerificationService.readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceSession.myUserId,
|
||||
requestID!!
|
||||
)
|
||||
testHelper.runBlockingTest {
|
||||
bobVerificationService.readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceSession.myUserId,
|
||||
requestID!!
|
||||
)
|
||||
}
|
||||
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
|
@ -606,19 +606,19 @@ class SASTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
// Start concurrent!
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.deviceId!!)
|
||||
testHelper.runBlockingTest {
|
||||
aliceVerificationService.requestKeyVerificationInDMs(
|
||||
methods = listOf(VerificationMethod.SAS),
|
||||
otherUserId = bobSession.myUserId,
|
||||
roomId = cryptoTestData.roomId
|
||||
)
|
||||
|
||||
bobVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
aliceSession.myUserId,
|
||||
aliceSession.sessionParams.deviceId!!)
|
||||
bobVerificationService.requestKeyVerificationInDMs(
|
||||
methods = listOf(VerificationMethod.SAS),
|
||||
otherUserId = aliceSession.myUserId,
|
||||
roomId = cryptoTestData.roomId
|
||||
)
|
||||
}
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: SasVerificationTransaction?
|
||||
|
|
|
@ -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!!
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
||||
package org.matrix.android.sdk.internal.crypto.verification
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.amshove.kluent.shouldBe
|
||||
|
@ -23,19 +23,13 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -147,50 +141,19 @@ class VerificationTest : InstrumentedTest {
|
|||
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
|
||||
)
|
||||
|
||||
// TODO Add tests without SAS
|
||||
|
||||
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
|
||||
bobSupportedMethods: List<VerificationMethod>,
|
||||
expectedResultForAlice: ExpectedResult,
|
||||
expectedResultForBob: ExpectedResult) {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
testHelper.doSync<Unit> { callback ->
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD,
|
||||
session = flowResponse.session
|
||||
)
|
||||
)
|
||||
}
|
||||
}, callback)
|
||||
}
|
||||
|
||||
testHelper.doSync<Unit> { callback ->
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = bobSession.myUserId,
|
||||
password = TestConstants.PASSWORD,
|
||||
session = flowResponse.session
|
||||
)
|
||||
)
|
||||
}
|
||||
}, callback)
|
||||
}
|
||||
cryptoTestHelper.initializeCrossSigning(aliceSession)
|
||||
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||
|
@ -202,6 +165,7 @@ class VerificationTest : InstrumentedTest {
|
|||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
// Step 4: Alice receive the ready request
|
||||
Timber.v("Alice is ready: ${pr.isReady}")
|
||||
if (pr.isReady) {
|
||||
aliceReadyPendingVerificationRequest = pr
|
||||
latch.countDown()
|
||||
|
@ -213,16 +177,19 @@ class VerificationTest : InstrumentedTest {
|
|||
val bobListener = object : VerificationService.Listener {
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// Step 2: Bob accepts the verification request
|
||||
bobVerificationService.readyPendingVerificationInDMs(
|
||||
bobSupportedMethods,
|
||||
aliceSession.myUserId,
|
||||
cryptoTestData.roomId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
Timber.v("Bob accepts the verification request")
|
||||
testHelper.runBlockingTest {
|
||||
bobVerificationService.readyPendingVerification(
|
||||
bobSupportedMethods,
|
||||
aliceSession.myUserId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
// Step 3: Bob is ready
|
||||
Timber.v("Bob is ready: ${pr.isReady}")
|
||||
if (pr.isReady) {
|
||||
bobReadyPendingVerificationRequest = pr
|
||||
latch.countDown()
|
||||
|
@ -233,7 +200,9 @@ class VerificationTest : InstrumentedTest {
|
|||
|
||||
val bobUserId = bobSession.myUserId
|
||||
// Step 1: Alice starts a verification request
|
||||
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
||||
testHelper.runBlockingTest {
|
||||
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
||||
}
|
||||
testHelper.await(latch)
|
||||
|
||||
aliceReadyPendingVerificationRequest!!.let { pr ->
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto
|
|||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||
|
@ -32,14 +32,12 @@ import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
|||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.internal.crypto.NewSessionListener
|
||||
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
||||
|
||||
interface CryptoService {
|
||||
|
||||
|
@ -49,9 +47,9 @@ interface CryptoService {
|
|||
|
||||
fun keysBackupService(): KeysBackupService
|
||||
|
||||
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
||||
suspend fun setDeviceName(deviceId: String, deviceName: String)
|
||||
|
||||
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
|
||||
suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
|
||||
|
||||
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
||||
|
||||
|
@ -61,11 +59,9 @@ interface CryptoService {
|
|||
|
||||
fun setWarnOnUnknownDevices(warn: Boolean)
|
||||
|
||||
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
|
||||
suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
||||
|
||||
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
||||
|
||||
fun getMyDevice(): CryptoDeviceInfo
|
||||
suspend fun getMyCryptoDevice(): CryptoDeviceInfo
|
||||
|
||||
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
||||
|
||||
|
@ -73,8 +69,6 @@ interface CryptoService {
|
|||
|
||||
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
|
||||
|
||||
fun getDeviceTrackingStatus(userId: String): Int
|
||||
|
||||
suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
|
||||
password: String,
|
||||
progressListener: ProgressListener?): ImportRoomKeysResult
|
||||
|
@ -83,7 +77,7 @@ interface CryptoService {
|
|||
|
||||
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
||||
|
||||
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||
|
||||
fun reRequestRoomKeyForEvent(event: Event)
|
||||
|
||||
|
@ -91,29 +85,26 @@ interface CryptoService {
|
|||
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
suspend fun fetchDevicesList(): List<DeviceInfo>
|
||||
|
||||
fun getMyDevicesInfo(): List<DeviceInfo>
|
||||
|
||||
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
|
||||
|
||||
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||
suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo
|
||||
|
||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||
suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||
|
||||
fun isRoomEncrypted(roomId: String): Boolean
|
||||
|
||||
fun encryptEventContent(eventContent: Content,
|
||||
suspend fun encryptEventContent(eventContent: Content,
|
||||
eventType: String,
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||
roomId: String): MXEncryptEventContentResult
|
||||
|
||||
fun discardOutboundSession(roomId: String)
|
||||
|
||||
@Throws(MXCryptoError::class)
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
fun getEncryptionAlgorithm(roomId: String): String?
|
||||
|
||||
|
@ -121,11 +112,11 @@ interface CryptoService {
|
|||
|
||||
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo>
|
||||
|
||||
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
||||
suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo>
|
||||
|
||||
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
|
||||
fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>>
|
||||
|
||||
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
||||
fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>>
|
||||
|
||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||
|
||||
|
@ -150,7 +141,7 @@ interface CryptoService {
|
|||
* Perform any background tasks that can be done before a message is ready to
|
||||
* send, in order to speed up sending of the message.
|
||||
*/
|
||||
fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>)
|
||||
suspend fun prepareToEncrypt(roomId: String)
|
||||
|
||||
/**
|
||||
* When LL all room members might not be loaded when setting up encryption.
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.crypto.crosssigning
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
@ -29,31 +28,30 @@ interface CrossSigningService {
|
|||
/**
|
||||
* Is our own device signed by our own cross signing identity
|
||||
*/
|
||||
fun isCrossSigningVerified(): Boolean
|
||||
suspend fun isCrossSigningVerified(): Boolean
|
||||
|
||||
// TODO this isn't used anywhere besides in tests?
|
||||
// Is this the local trust concept that we have for devices?
|
||||
fun isUserTrusted(otherUserId: String): Boolean
|
||||
suspend fun isUserTrusted(otherUserId: String): Boolean
|
||||
|
||||
/**
|
||||
* Will not force a download of the key, but will verify signatures trust chain.
|
||||
* Checks that my trusted user key has signed the other user UserKey
|
||||
*/
|
||||
fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||
suspend fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||
|
||||
/**
|
||||
* Initialize cross signing for this user.
|
||||
* Users needs to enter credentials
|
||||
*/
|
||||
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?,
|
||||
callback: MatrixCallback<Unit>)
|
||||
suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
|
||||
|
||||
/**
|
||||
* Does our own user have a valid cross signing identity uploaded.
|
||||
*
|
||||
* In other words has any of our devices uploaded public cross signing keys to the server.
|
||||
*/
|
||||
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
||||
suspend fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
||||
|
||||
/**
|
||||
* Inject the private cross signing keys, likely from backup, into our store.
|
||||
|
@ -62,25 +60,25 @@ interface CrossSigningService {
|
|||
* by the server and if they do so
|
||||
*/
|
||||
suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||
uskKeyPrivateKey: String?,
|
||||
sskPrivateKey: String?): UserTrustResult
|
||||
uskKeyPrivateKey: String?,
|
||||
sskPrivateKey: String?): UserTrustResult
|
||||
|
||||
/**
|
||||
* Get the public cross signing keys for the given user
|
||||
*
|
||||
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
|
||||
*/
|
||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||
suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||
|
||||
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
||||
fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>>
|
||||
|
||||
/** Get our own public cross signing keys */
|
||||
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||
suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||
|
||||
/** Get our own private cross signing keys */
|
||||
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||
suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||
|
||||
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
|
||||
fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>>
|
||||
|
||||
/**
|
||||
* Can we sign our other devices or other users?
|
||||
|
@ -93,11 +91,10 @@ interface CrossSigningService {
|
|||
fun allPrivateKeysKnown(): Boolean
|
||||
|
||||
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
||||
fun trustUser(otherUserId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
suspend fun trustUser(otherUserId: String)
|
||||
|
||||
/** Mark our own master key as trusted */
|
||||
fun markMyMasterKeyAsTrusted()
|
||||
suspend fun markMyMasterKeyAsTrusted()
|
||||
|
||||
/**
|
||||
* Sign one of your devices and upload the signature
|
||||
|
@ -114,10 +111,10 @@ interface CrossSigningService {
|
|||
* using the self-signing key for our own devices or using the user-signing key and the master
|
||||
* key of another user.
|
||||
*/
|
||||
fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
// TODO what is locallyTrusted used for?
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
suspend fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
// TODO what is locallyTrusted used for?
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
|
||||
// FIXME Those method do not have to be in the service
|
||||
// TODO those three methods doesn't seem to be used anywhere?
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
|||
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||
|
||||
interface KeysBackupService {
|
||||
|
||||
/**
|
||||
* Retrieve the current version of the backup from the homeserver
|
||||
*
|
||||
|
@ -45,12 +46,12 @@ interface KeysBackupService {
|
|||
/**
|
||||
* Facility method to get the total number of locally stored keys
|
||||
*/
|
||||
fun getTotalNumbersOfKeys(): Int
|
||||
suspend fun getTotalNumbersOfKeys(): Int
|
||||
|
||||
/**
|
||||
* Facility method to get the number of backed up keys
|
||||
*/
|
||||
fun getTotalNumbersOfBackedUpKeys(): Int
|
||||
suspend fun getTotalNumbersOfBackedUpKeys(): Int
|
||||
|
||||
// /**
|
||||
// * Start to back up keys immediately.
|
||||
|
@ -71,7 +72,7 @@ interface KeysBackupService {
|
|||
/**
|
||||
* Return the current progress of the backup
|
||||
*/
|
||||
fun getBackupProgress(progressListener: ProgressListener)
|
||||
suspend fun getBackupProgress(progressListener: ProgressListener)
|
||||
|
||||
/**
|
||||
* Get information about a backup version defined on the homeserver.
|
||||
|
@ -128,7 +129,7 @@ interface KeysBackupService {
|
|||
* Ask if the backup on the server contains keys that we may do not have locally.
|
||||
* This should be called when entering in the state READY_TO_BACKUP
|
||||
*/
|
||||
fun canRestoreKeys(): Boolean
|
||||
suspend fun canRestoreKeys(): Boolean
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
|
@ -199,7 +200,7 @@ interface KeysBackupService {
|
|||
|
||||
// For gossiping
|
||||
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||
suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||
|
||||
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -26,15 +26,15 @@ interface QrCodeVerificationTransaction : VerificationTransaction {
|
|||
/**
|
||||
* Call when you have scan the other user QR code
|
||||
*/
|
||||
fun userHasScannedOtherQrCode(otherQrCodeText: String)
|
||||
suspend fun userHasScannedOtherQrCode(otherQrCodeText: String)
|
||||
|
||||
/**
|
||||
* Call when you confirm that other user has scanned your QR code
|
||||
*/
|
||||
fun otherUserScannedMyQrCode()
|
||||
suspend fun otherUserScannedMyQrCode()
|
||||
|
||||
/**
|
||||
* Call when you do not confirm that other user has scanned your QR code
|
||||
*/
|
||||
fun otherUserDidNotScannedMyQrCode()
|
||||
suspend fun otherUserDidNotScannedMyQrCode()
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ interface SasVerificationTransaction : VerificationTransaction {
|
|||
|
||||
fun supportsEmoji(): Boolean
|
||||
|
||||
fun supportsDecimal(): Boolean
|
||||
|
||||
fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
|
||||
|
||||
fun getDecimalCodeRepresentation(): String
|
||||
|
@ -30,9 +28,9 @@ interface SasVerificationTransaction : VerificationTransaction {
|
|||
* To be called by the client when the user has verified that
|
||||
* both short codes do match
|
||||
*/
|
||||
fun userHasVerifiedShortCode()
|
||||
suspend fun userHasVerifiedShortCode()
|
||||
|
||||
fun acceptVerification()
|
||||
suspend fun acceptVerification()
|
||||
|
||||
fun shortCodeDoesNotMatch()
|
||||
suspend fun shortCodeDoesNotMatch()
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.crypto.verification
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||
|
||||
/**
|
||||
|
@ -36,7 +35,7 @@ interface VerificationService {
|
|||
/**
|
||||
* Mark this device as verified manually
|
||||
*/
|
||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||
suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||
|
||||
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
|
||||
|
||||
|
@ -46,54 +45,37 @@ interface VerificationService {
|
|||
|
||||
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
|
||||
|
||||
fun beginKeyVerification(method: VerificationMethod,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
transactionId: String?): String?
|
||||
/**
|
||||
* Request key verification with another user via room events (instead of the to-device API).
|
||||
*/
|
||||
suspend fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||
|
||||
/**
|
||||
* Request key verification with another user via room events (instead of the to-device API)
|
||||
* Request a self key verification using to-device API (instead of room events).
|
||||
*/
|
||||
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||
|
||||
fun cancelVerificationRequest(request: PendingVerificationRequest)
|
||||
suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest
|
||||
|
||||
/**
|
||||
* Request a key verification from another user using toDevice events.
|
||||
* You should call this method after receiving a verification request.
|
||||
* Accept the verification request advertising the given methods as supported
|
||||
* Returns false if the request is unknown or transaction is not ready.
|
||||
*/
|
||||
fun requestKeyVerification(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
otherDevices: List<String>?): PendingVerificationRequest
|
||||
suspend fun readyPendingVerification(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
transactionId: String): Boolean
|
||||
|
||||
fun declineVerificationRequestInDMs(otherUserId: String,
|
||||
transactionId: String,
|
||||
roomId: String)
|
||||
suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
|
||||
|
||||
// Only SAS method is supported for the moment
|
||||
// TODO Parameter otherDeviceId should be removed in this case
|
||||
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
||||
transactionId: String,
|
||||
roomId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String): String
|
||||
suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String)
|
||||
|
||||
/**
|
||||
* Returns false if the request is unknown
|
||||
*/
|
||||
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
transactionId: String): Boolean
|
||||
suspend fun beginKeyVerification(method: VerificationMethod,
|
||||
otherUserId: String,
|
||||
transactionId: String): String?
|
||||
|
||||
/**
|
||||
* Returns false if the request is unknown
|
||||
*/
|
||||
fun readyPendingVerification(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
transactionId: String): Boolean
|
||||
suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String?
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
|
@ -137,6 +119,4 @@ interface VerificationService {
|
|||
return age in tooInThePast..tooInTheFuture
|
||||
}
|
||||
}
|
||||
|
||||
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
|
||||
}
|
||||
|
|
|
@ -30,9 +30,9 @@ interface VerificationTransaction {
|
|||
/**
|
||||
* User wants to cancel the transaction
|
||||
*/
|
||||
fun cancel()
|
||||
suspend fun cancel()
|
||||
|
||||
fun cancel(code: CancelCode)
|
||||
suspend fun cancel(code: CancelCode)
|
||||
|
||||
fun isToDeviceTransport(): Boolean
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ object EventType {
|
|||
const val SEND_SECRET = "m.secret.send"
|
||||
|
||||
// Interactive key verification
|
||||
const val KEY_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||
const val KEY_VERIFICATION_START = "m.key.verification.start"
|
||||
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"
|
||||
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.room.model.message
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
||||
object MessageType {
|
||||
const val MSGTYPE_TEXT = "m.text"
|
||||
const val MSGTYPE_EMOTE = "m.emote"
|
||||
|
@ -26,7 +28,7 @@ object MessageType {
|
|||
const val MSGTYPE_LOCATION = "m.location"
|
||||
const val MSGTYPE_FILE = "m.file"
|
||||
|
||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||
const val MSGTYPE_VERIFICATION_REQUEST = EventType.KEY_VERIFICATION_REQUEST
|
||||
|
||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||
// Because sticker isn't a message type but a event type without msgtype field
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -22,18 +22,17 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.paging.PagedList
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
|
@ -55,7 +54,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
|||
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
|
||||
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
||||
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||
|
@ -65,8 +63,9 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
|||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
||||
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
|
||||
|
@ -76,14 +75,9 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
|||
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.TaskThread
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import timber.log.Timber
|
||||
import uniffi.olm.Request
|
||||
import uniffi.olm.RequestType
|
||||
|
@ -130,9 +124,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val sender: RequestSender,
|
||||
private val requestSender: RequestSender,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val verificationService: RustVerificationService,
|
||||
private val keysBackupService: RustKeyBackupService,
|
||||
|
@ -153,7 +146,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
// Locks for some of our operations
|
||||
private val keyClaimLock: Mutex = Mutex()
|
||||
private val outgoingRequestsLock: Mutex = Mutex()
|
||||
private val outgoingRequestsProcessor = OutgoingRequestsProcessor(
|
||||
requestSender = requestSender,
|
||||
coroutineScope = cryptoCoroutineScope,
|
||||
cryptoSessionInfoProvider = cryptoSessionInfoProvider,
|
||||
shieldComputer = crossSigningService::shieldForGroup
|
||||
)
|
||||
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap()
|
||||
|
||||
fun onStateEvent(roomId: String, event: Event) {
|
||||
|
@ -166,51 +164,33 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
fun onLiveEvent(roomId: String, event: Event) {
|
||||
if (event.isStateEvent()) {
|
||||
when (event.getClearType()) {
|
||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
else -> cryptoCoroutineScope.launch {
|
||||
this@DefaultCryptoService.verificationService.onEvent(event)
|
||||
when (event.getClearType()) {
|
||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
}
|
||||
} else {
|
||||
cryptoCoroutineScope.launch {
|
||||
verificationService.onEvent(roomId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val gossipingBuffer = mutableListOf<Event>()
|
||||
|
||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||
setDeviceNameTask
|
||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// bg refresh of crypto device
|
||||
cryptoCoroutineScope.launch {
|
||||
try {
|
||||
downloadKeys(listOf(userId), true)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
|
||||
}
|
||||
}
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun setDeviceName(deviceId: String, deviceName: String) {
|
||||
val params = SetDeviceNameTask.Params(deviceId, deviceName)
|
||||
setDeviceNameTask.execute(params)
|
||||
try {
|
||||
downloadKeys(listOf(userId), true)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
|
||||
deleteDeviceTask
|
||||
.configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||
val params = DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)
|
||||
deleteDeviceTask.execute(params)
|
||||
}
|
||||
|
||||
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
||||
|
@ -218,27 +198,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return if (longFormat) "Rust SDK 0.3" else "0.3"
|
||||
}
|
||||
|
||||
override fun getMyDevice(): CryptoDeviceInfo {
|
||||
return runBlocking { olmMachine.ownDevice() }
|
||||
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo {
|
||||
return olmMachine.ownDevice()
|
||||
}
|
||||
|
||||
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||
getDevicesTask
|
||||
.configureWith {
|
||||
// this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<DevicesListResponse> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
// Save in local DB
|
||||
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun fetchDevicesList(): List<DeviceInfo> {
|
||||
val devicesList = tryOrNull {
|
||||
getDevicesTask.execute(Unit).devices
|
||||
}.orEmpty()
|
||||
cryptoStore.saveMyDevicesInfo(devicesList)
|
||||
return devicesList
|
||||
}
|
||||
|
||||
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
|
||||
|
@ -249,16 +218,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return cryptoStore.getMyDevicesInfo()
|
||||
}
|
||||
|
||||
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||
getDeviceInfoTask
|
||||
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
|
||||
val params = GetDeviceInfoTask.Params(deviceId)
|
||||
return getDeviceInfoTask.execute(params)
|
||||
}
|
||||
|
||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||
override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||
return if (onlyBackedUp) {
|
||||
keysBackupService.getTotalNumbersOfBackedUpKeys()
|
||||
} else {
|
||||
|
@ -267,20 +232,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the tracking status
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return the tracking status
|
||||
*/
|
||||
override fun getDeviceTrackingStatus(userId: String): Int {
|
||||
return if (olmMachine.isUserTracked(userId)) {
|
||||
3
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if the MXCrypto is started
|
||||
*
|
||||
|
@ -299,29 +250,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*/
|
||||
fun start() {
|
||||
internalStart()
|
||||
// Just update
|
||||
fetchDevicesList(NoOpMatrixCallback())
|
||||
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
cryptoCoroutineScope.launch {
|
||||
// Just update
|
||||
fetchDevicesList()
|
||||
cryptoStore.tidyUpDataBase()
|
||||
}
|
||||
}
|
||||
|
||||
fun ensureDevice() {
|
||||
cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
|
||||
// Open the store
|
||||
cryptoStore.open()
|
||||
|
||||
// this can throw if no backup
|
||||
/*
|
||||
TODO
|
||||
tryOrNull {
|
||||
keysBackupService.checkAndStartKeysBackup()
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalStart() {
|
||||
if (isStarted.get() || isStarting.get()) {
|
||||
return
|
||||
|
@ -355,9 +290,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
/**
|
||||
* Close the crypto
|
||||
*/
|
||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||
fun close() {
|
||||
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
||||
cryptoStore.close()
|
||||
cryptoCoroutineScope.launch {
|
||||
withContext(coroutineDispatchers.crypto + NonCancellable) {
|
||||
cryptoStore.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always enabled on Matrix Android SDK2
|
||||
|
@ -380,7 +319,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*/
|
||||
suspend fun onSyncCompleted() {
|
||||
if (isStarted()) {
|
||||
sendOutgoingRequests()
|
||||
outgoingRequestsProcessor.process(olmMachine)
|
||||
// This isn't a copy paste error. Sending the outgoing requests may
|
||||
// claim one-time keys and establish 1-to-1 Olm sessions with devices, while some
|
||||
// outgoing requests are waiting for an Olm session to be established (e.g. forwarding
|
||||
|
@ -389,7 +328,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// The second call sends out those requests that are waiting for the
|
||||
// keys claim request to be sent out.
|
||||
// This could be omitted but then devices might be waiting for the next
|
||||
sendOutgoingRequests()
|
||||
outgoingRequestsProcessor.process(olmMachine)
|
||||
|
||||
keysBackupService.maybeBackupKeys()
|
||||
}
|
||||
|
@ -410,41 +349,19 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @param userId the user id
|
||||
* @param deviceId the device id
|
||||
*/
|
||||
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
||||
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||
runBlocking {
|
||||
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId, deviceId)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
||||
if (userId.isEmpty() || deviceId.isNullOrEmpty()) return null
|
||||
return olmMachine.getCryptoDeviceInfo(userId, deviceId)
|
||||
}
|
||||
|
||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
return runBlocking {
|
||||
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId)
|
||||
}
|
||||
override suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo> {
|
||||
return olmMachine.getCryptoDeviceInfo(userId)
|
||||
}
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
||||
return getLiveCryptoDeviceInfo(listOf(userId))
|
||||
}
|
||||
override fun getLiveCryptoDeviceInfoList(userId: String) = getLiveCryptoDeviceInfoList(listOf(userId))
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||
return runBlocking {
|
||||
this@DefaultCryptoService.olmMachine.getLiveDevices(userIds) // ?: LiveDevice(userIds, deviceObserver)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the blocked/verified state of the given device.
|
||||
*
|
||||
* @param trustLevel the new trust level
|
||||
* @param userId the owner of the device
|
||||
* @param deviceId the unique identifier for the device.
|
||||
*/
|
||||
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||
// TODO
|
||||
override fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
|
||||
return olmMachine.getLiveDevices(userIds)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -503,8 +420,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
/**
|
||||
* @return the stored device keys for a user.
|
||||
*/
|
||||
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||
return this.getCryptoDeviceInfo(userId).toMutableList()
|
||||
override suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||
return this.getCryptoDeviceInfoList(userId).toMutableList()
|
||||
}
|
||||
|
||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||
|
@ -533,34 +450,29 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @param eventContent the content of the event.
|
||||
* @param eventType the type of the event.
|
||||
* @param roomId the room identifier the event will be sent.
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
override fun encryptEventContent(eventContent: Content,
|
||||
eventType: String,
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||
override suspend fun encryptEventContent(eventContent: Content,
|
||||
eventType: String,
|
||||
roomId: String): MXEncryptEventContentResult {
|
||||
// moved to crypto scope to have up to date values
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
return withContext(coroutineDispatchers.crypto) {
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
|
||||
if (algorithm != null) {
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
val t0 = System.currentTimeMillis()
|
||||
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
|
||||
runCatching {
|
||||
measureTimeMillis {
|
||||
preshareRoomKey(roomId, userIds)
|
||||
}.also {
|
||||
Timber.d("Shared room key in room $roomId took $it ms")
|
||||
}
|
||||
val content = encrypt(roomId, eventType, eventContent)
|
||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||
}.foldToCallback(callback)
|
||||
measureTimeMillis {
|
||||
preshareRoomKey(roomId, userIds)
|
||||
}.also {
|
||||
Timber.d("Shared room key in room $roomId took $it ms")
|
||||
}
|
||||
val content = encrypt(roomId, eventType, eventContent)
|
||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||
} else {
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
||||
throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -577,22 +489,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @return the MXEventDecryptionResult data, or throw in case of error
|
||||
*/
|
||||
@Throws(MXCryptoError::class)
|
||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
return runBlocking {
|
||||
olmMachine.decryptRoomEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an event asynchronously
|
||||
*
|
||||
* @param event the raw event.
|
||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||
* @param callback the callback to return data or null
|
||||
*/
|
||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
||||
// This isn't really used anywhere, maybe just remove it?
|
||||
// TODO
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
return olmMachine.decryptRoomEvent(event)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -616,7 +514,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
||||
// } finally {
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
olmMachine.updateTrackedUsers(userIds)
|
||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds)
|
||||
// }
|
||||
}
|
||||
|
@ -721,7 +618,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
this.keysBackupService.onSecretKeyGossip(secretContent.secretValue)
|
||||
}
|
||||
else -> {
|
||||
this.verificationService.onEvent(event)
|
||||
this.verificationService.onEvent(null, event)
|
||||
}
|
||||
}
|
||||
liveEventManager.get().dispatchOnLiveToDevice(event)
|
||||
|
@ -730,26 +627,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>) {
|
||||
keyClaimLock.withLock {
|
||||
val request = this.olmMachine.getMissingSessions(roomMembers)
|
||||
// This request can only be a keys claim request.
|
||||
if (request != null) {
|
||||
when (request) {
|
||||
is Request.KeysClaim -> {
|
||||
claimKeys(request)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val keyShareLock = roomKeyShareLocks.getOrPut(roomId, { Mutex() })
|
||||
claimMissingKeys(roomMembers)
|
||||
val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() }
|
||||
var sharedKey = false
|
||||
|
||||
keyShareLock.withLock {
|
||||
coroutineScope {
|
||||
this@DefaultCryptoService.olmMachine.shareRoomKey(roomId, roomMembers).map {
|
||||
olmMachine.shareRoomKey(roomId, roomMembers).map {
|
||||
when (it) {
|
||||
is Request.ToDevice -> {
|
||||
sharedKey = true
|
||||
|
@ -775,19 +658,35 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun claimMissingKeys(roomMembers: List<String>) = keyClaimLock.withLock {
|
||||
val request = this.olmMachine.getMissingSessions(roomMembers)
|
||||
// This request can only be a keys claim request.
|
||||
when (request) {
|
||||
is Request.KeysClaim -> {
|
||||
claimKeys(request)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun encrypt(roomId: String, eventType: String, content: Content): Content {
|
||||
return olmMachine.encrypt(roomId, eventType, content)
|
||||
}
|
||||
|
||||
private suspend fun uploadKeys(request: Request.KeysUpload) {
|
||||
val response = this.sender.uploadKeys(request)
|
||||
this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
|
||||
try {
|
||||
val response = requestSender.uploadKeys(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO uploadKeys(): error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun queryKeys(request: Request.KeysQuery) {
|
||||
try {
|
||||
val response = this.sender.queryKeys(request)
|
||||
this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
|
||||
val response = requestSender.queryKeys(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
|
||||
|
||||
// Update the shields!
|
||||
cryptoCoroutineScope.launch {
|
||||
|
@ -798,69 +697,44 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO doKeyDownloadForUsers(): error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendToDevice(request: Request.ToDevice) {
|
||||
this.sender.sendToDevice(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
|
||||
try {
|
||||
requestSender.sendToDevice(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendToDevice(): error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun claimKeys(request: Request.KeysClaim) {
|
||||
val response = this.sender.claimKeys(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
|
||||
try {
|
||||
val response = requestSender.claimKeys(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO claimKeys(): error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun signatureUpload(request: Request.SignatureUpload) {
|
||||
this.sender.sendSignatureUpload(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}")
|
||||
try {
|
||||
val response = requestSender.sendSignatureUpload(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, response)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO signatureUpload(): error")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendOutgoingRequests() {
|
||||
outgoingRequestsLock.withLock {
|
||||
coroutineScope {
|
||||
olmMachine.outgoingRequests().map {
|
||||
when (it) {
|
||||
is Request.KeysUpload -> {
|
||||
async {
|
||||
uploadKeys(it)
|
||||
}
|
||||
}
|
||||
is Request.KeysQuery -> {
|
||||
async {
|
||||
queryKeys(it)
|
||||
}
|
||||
}
|
||||
is Request.ToDevice -> {
|
||||
async {
|
||||
sendToDevice(it)
|
||||
}
|
||||
}
|
||||
is Request.KeysClaim -> {
|
||||
async {
|
||||
claimKeys(it)
|
||||
}
|
||||
}
|
||||
is Request.RoomMessage -> {
|
||||
async {
|
||||
sender.sendRoomMessage(it)
|
||||
}
|
||||
}
|
||||
is Request.SignatureUpload -> {
|
||||
async {
|
||||
signatureUpload(it)
|
||||
}
|
||||
}
|
||||
is Request.KeysBackup -> {
|
||||
async {
|
||||
// The rust-sdk won't ever produce KeysBackup requests here,
|
||||
// those only get explicitly created.
|
||||
}
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
private suspend fun sendRoomMessage(request: Request.RoomMessage) {
|
||||
try {
|
||||
Timber.v("SendRoomMessage: $request")
|
||||
val response = requestSender.sendRoomMessage(request)
|
||||
olmMachine.markRequestAsSent(request.requestId, RequestType.ROOM_MESSAGE, response)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendRoomMessage(): error")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -991,18 +865,17 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
val cancellation = requestPair.cancellation
|
||||
val request = requestPair.keyRequest
|
||||
|
||||
if (cancellation != null) {
|
||||
when (cancellation) {
|
||||
is Request.ToDevice -> {
|
||||
sendToDevice(cancellation)
|
||||
}
|
||||
when (cancellation) {
|
||||
is Request.ToDevice -> {
|
||||
sendToDevice(cancellation)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
when (request) {
|
||||
is Request.ToDevice -> {
|
||||
sendToDevice(request)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1082,18 +955,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
cryptoStore.logDbUsageInfo()
|
||||
}
|
||||
|
||||
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
override suspend fun prepareToEncrypt(roomId: String) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
||||
// Ensure to load all room members
|
||||
try {
|
||||
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
|
||||
callback.onFailure(failure)
|
||||
return@launch
|
||||
throw failure
|
||||
}
|
||||
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
|
@ -1101,19 +972,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
if (algorithm == null) {
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
||||
callback.onFailure(IllegalArgumentException("Missing algorithm"))
|
||||
return@launch
|
||||
throw IllegalArgumentException("Missing algorithm")
|
||||
}
|
||||
|
||||
runCatching {
|
||||
try {
|
||||
preshareRoomKey(roomId, userIds)
|
||||
}.fold(
|
||||
{ callback.onSuccess(Unit) },
|
||||
{
|
||||
Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
|
||||
callback.onFailure(it)
|
||||
}
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to PreshareRoomKey")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||
import uniffi.olm.CryptoStoreException
|
||||
import uniffi.olm.OlmMachine
|
||||
|
@ -39,16 +40,17 @@ internal class Device(
|
|||
private val machine: OlmMachine,
|
||||
private var inner: InnerDevice,
|
||||
private val sender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val listeners: ArrayList<VerificationService.Listener>
|
||||
) {
|
||||
@Throws(CryptoStoreException::class)
|
||||
private suspend fun refreshData() {
|
||||
val device = withContext(Dispatchers.IO) {
|
||||
val device = withContext(coroutineDispatchers.io) {
|
||||
machine.getDevice(inner.userId, inner.deviceId)
|
||||
}
|
||||
|
||||
if (device != null) {
|
||||
this.inner = device
|
||||
inner = device
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,12 +68,12 @@ internal class Device(
|
|||
@Throws(CryptoStoreException::class)
|
||||
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
|
||||
val stringMethods = prepareMethods(methods)
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val result = withContext(coroutineDispatchers.io) {
|
||||
machine.requestVerificationWithDevice(inner.userId, inner.deviceId, stringMethods)
|
||||
}
|
||||
|
||||
return if (result != null) {
|
||||
this.sender.sendVerificationRequest(result.request)
|
||||
sender.sendVerificationRequest(result.request)
|
||||
result.verification
|
||||
} else {
|
||||
null
|
||||
|
@ -89,14 +91,18 @@ internal class Device(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun startVerification(): SasVerification? {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val result = withContext(coroutineDispatchers.io) {
|
||||
machine.startSasWithDevice(inner.userId, inner.deviceId)
|
||||
}
|
||||
|
||||
return if (result != null) {
|
||||
this.sender.sendVerificationRequest(result.request)
|
||||
sender.sendVerificationRequest(result.request)
|
||||
SasVerification(
|
||||
this.machine, result.sas, this.sender, this.listeners,
|
||||
machine = machine,
|
||||
inner = result.sas,
|
||||
sender = sender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = listeners
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
@ -111,7 +117,7 @@ internal class Device(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun markAsTrusted() {
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
|
||||
}
|
||||
}
|
||||
|
@ -127,11 +133,11 @@ internal class Device(
|
|||
*/
|
||||
@Throws(SignatureException::class)
|
||||
suspend fun verify(): Boolean {
|
||||
val request = withContext(Dispatchers.IO) {
|
||||
val request = withContext(coroutineDispatchers.io) {
|
||||
machine.verifyDevice(inner.userId, inner.deviceId)
|
||||
}
|
||||
|
||||
this.sender.sendSignatureUpload(request)
|
||||
sender.sendSignatureUpload(request)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -151,20 +157,20 @@ internal class Device(
|
|||
* This will not fetch out fresh data from the Rust side.
|
||||
**/
|
||||
internal fun toCryptoDeviceInfo(): CryptoDeviceInfo {
|
||||
val keys = this.inner.keys.map { (keyId, key) -> "$keyId:$this.inner.deviceId" to key }.toMap()
|
||||
val keys = inner.keys.map { (keyId, key) -> "$keyId:$inner.deviceId" to key }.toMap()
|
||||
|
||||
return CryptoDeviceInfo(
|
||||
this.inner.deviceId,
|
||||
this.inner.userId,
|
||||
this.inner.algorithms,
|
||||
keys,
|
||||
deviceId = inner.deviceId,
|
||||
userId = inner.userId,
|
||||
algorithms = inner.algorithms,
|
||||
keys = keys,
|
||||
// The Kotlin side doesn't need to care about signatures,
|
||||
// so we're not filling this out
|
||||
mapOf(),
|
||||
UnsignedDeviceInfo(this.inner.displayName),
|
||||
DeviceTrustLevel(crossSigningVerified = this.inner.crossSigningTrusted, locallyVerified = this.inner.locallyTrusted),
|
||||
this.inner.isBlocked,
|
||||
signatures = mapOf(),
|
||||
unsigned = UnsignedDeviceInfo(inner.displayName),
|
||||
trustLevel = DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted),
|
||||
isBlocked = inner.isBlocked,
|
||||
// TODO
|
||||
null)
|
||||
firstTimeSeenLocalTs = null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import com.squareup.moshi.Moshi
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
|
@ -36,6 +38,7 @@ import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
|||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
|
@ -44,8 +47,8 @@ import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
|||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||
import timber.log.Timber
|
||||
import uniffi.olm.BackupKeys
|
||||
|
@ -64,7 +67,6 @@ import uniffi.olm.setLogger
|
|||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import uniffi.olm.OlmMachine as InnerMachine
|
||||
import uniffi.olm.ProgressListener as RustProgressListener
|
||||
import uniffi.olm.UserIdentity as RustUserIdentity
|
||||
|
@ -81,138 +83,71 @@ private class CryptoProgressListener(private val listener: ProgressListener?) :
|
|||
}
|
||||
}
|
||||
|
||||
internal class LiveDevice(
|
||||
internal var userIds: List<String>,
|
||||
private var observer: DeviceUpdateObserver
|
||||
) : MutableLiveData<List<CryptoDeviceInfo>>() {
|
||||
private data class UserIdentityCollector(val userId: String, val collector: SendChannel<Optional<MXCrossSigningInfo>>)
|
||||
: SendChannel<Optional<MXCrossSigningInfo>> by collector
|
||||
private data class DevicesCollector(val userIds: List<String>, val collector: SendChannel<List<CryptoDeviceInfo>>)
|
||||
: SendChannel<List<CryptoDeviceInfo>> by collector
|
||||
private typealias PrivateKeysCollector = SendChannel<Optional<PrivateKeysInfo>>
|
||||
|
||||
override fun onActive() {
|
||||
observer.addDeviceUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
observer.removeDeviceUpdateListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal class LiveUserIdentity(
|
||||
internal var userId: String,
|
||||
private var observer: UserIdentityUpdateObserver
|
||||
) : MutableLiveData<Optional<MXCrossSigningInfo>>() {
|
||||
override fun onActive() {
|
||||
observer.addUserIdentityUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
observer.removeUserIdentityUpdateListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal class LivePrivateCrossSigningKeys(
|
||||
private var observer: PrivateCrossSigningKeysUpdateObserver,
|
||||
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
|
||||
|
||||
override fun onActive() {
|
||||
observer.addUserIdentityUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
observer.removeUserIdentityUpdateListener(this)
|
||||
}
|
||||
private class FlowCollectors {
|
||||
val userIdentityCollectors = ArrayList<UserIdentityCollector>()
|
||||
val privateKeyCollectors = ArrayList<PrivateKeysCollector>()
|
||||
val deviceCollectors = ArrayList<DevicesCollector>()
|
||||
}
|
||||
|
||||
fun setRustLogger() {
|
||||
setLogger(CryptoLogger() as Logger)
|
||||
}
|
||||
|
||||
internal class DeviceUpdateObserver {
|
||||
internal val listeners = ConcurrentHashMap<LiveDevice, List<String>>()
|
||||
|
||||
fun addDeviceUpdateListener(device: LiveDevice) {
|
||||
listeners[device] = device.userIds
|
||||
}
|
||||
|
||||
fun removeDeviceUpdateListener(device: LiveDevice) {
|
||||
listeners.remove(device)
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserIdentityUpdateObserver {
|
||||
internal val listeners = ConcurrentHashMap<LiveUserIdentity, String>()
|
||||
|
||||
fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
|
||||
listeners[userIdentity] = userIdentity.userId
|
||||
}
|
||||
|
||||
fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
|
||||
listeners.remove(userIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
internal class PrivateCrossSigningKeysUpdateObserver {
|
||||
internal val listeners = ConcurrentHashMap<LivePrivateCrossSigningKeys, Unit>()
|
||||
|
||||
fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
|
||||
listeners[liveKeys] = Unit
|
||||
}
|
||||
|
||||
fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
|
||||
listeners.remove(liveKeys)
|
||||
}
|
||||
}
|
||||
|
||||
internal class OlmMachine(
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
path: File,
|
||||
deviceObserver: DeviceUpdateObserver,
|
||||
private val requestSender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val moshi: Moshi
|
||||
) {
|
||||
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
|
||||
private val deviceUpdateObserver = deviceObserver
|
||||
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
|
||||
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
|
||||
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString(), null)
|
||||
internal val verificationListeners = ArrayList<VerificationService.Listener>()
|
||||
private val flowCollectors = FlowCollectors()
|
||||
|
||||
/** Get our own user ID. */
|
||||
fun userId(): String {
|
||||
return this.inner.userId()
|
||||
return inner.userId()
|
||||
}
|
||||
|
||||
/** Get our own device ID. */
|
||||
fun deviceId(): String {
|
||||
return this.inner.deviceId()
|
||||
return inner.deviceId()
|
||||
}
|
||||
|
||||
/** Get our own public identity keys ID. */
|
||||
fun identityKeys(): Map<String, String> {
|
||||
return this.inner.identityKeys()
|
||||
return inner.identityKeys()
|
||||
}
|
||||
|
||||
fun inner(): InnerMachine {
|
||||
return this.inner
|
||||
return inner
|
||||
}
|
||||
|
||||
/** Update all of our live device listeners. */
|
||||
private suspend fun updateLiveDevices() {
|
||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
||||
val devices = getCryptoDeviceInfo(users)
|
||||
liveDevice.postValue(devices)
|
||||
for (deviceCollector in flowCollectors.deviceCollectors) {
|
||||
val devices = getCryptoDeviceInfo(deviceCollector.userIds)
|
||||
deviceCollector.trySend(devices)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateLiveUserIdentities() {
|
||||
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) {
|
||||
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
||||
liveIdentity.postValue(identity)
|
||||
for (userIdentityCollector in flowCollectors.userIdentityCollectors) {
|
||||
val identity = getIdentity(userIdentityCollector.userId)?.toMxCrossSigningInfo()
|
||||
userIdentityCollector.trySend(identity.toOptional())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateLivePrivateKeys() {
|
||||
val keys = this.exportCrossSigningKeys().toOptional()
|
||||
|
||||
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) {
|
||||
liveKeys.postValue(keys)
|
||||
val keys = exportCrossSigningKeys().toOptional()
|
||||
for (privateKeyCollector in flowCollectors.privateKeyCollectors) {
|
||||
privateKeyCollector.trySend(keys)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,18 +155,18 @@ internal class OlmMachine(
|
|||
* Get our own device info as [CryptoDeviceInfo].
|
||||
*/
|
||||
suspend fun ownDevice(): CryptoDeviceInfo {
|
||||
val deviceId = this.deviceId()
|
||||
val deviceId = deviceId()
|
||||
|
||||
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
||||
val keys = identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
||||
|
||||
val crossSigningVerified = when (val ownIdentity = this.getIdentity(this.userId())) {
|
||||
val crossSigningVerified = when (val ownIdentity = getIdentity(userId())) {
|
||||
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
|
||||
else -> false
|
||||
}
|
||||
|
||||
return CryptoDeviceInfo(
|
||||
this.deviceId(),
|
||||
this.userId(),
|
||||
deviceId(),
|
||||
userId(),
|
||||
// TODO pass the algorithms here.
|
||||
listOf(),
|
||||
keys,
|
||||
|
@ -251,7 +186,7 @@ internal class OlmMachine(
|
|||
* @return the list of requests that needs to be sent to the homeserver
|
||||
*/
|
||||
suspend fun outgoingRequests(): List<Request> =
|
||||
withContext(Dispatchers.IO) { inner.outgoingRequests() }
|
||||
withContext(coroutineDispatchers.io) { inner.outgoingRequests() }
|
||||
|
||||
/**
|
||||
* Mark a request that was sent to the server as sent.
|
||||
|
@ -268,9 +203,8 @@ internal class OlmMachine(
|
|||
requestType: RequestType,
|
||||
responseBody: String
|
||||
) =
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
inner.markRequestAsSent(requestId, requestType, responseBody)
|
||||
|
||||
if (requestType == RequestType.KEYS_QUERY) {
|
||||
updateLiveDevices()
|
||||
updateLiveUserIdentities()
|
||||
|
@ -296,7 +230,7 @@ internal class OlmMachine(
|
|||
deviceChanges: DeviceListResponse?,
|
||||
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
||||
): ToDeviceSyncResponse {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
val response = withContext(coroutineDispatchers.io) {
|
||||
val counts: MutableMap<String, Int> = mutableMapOf()
|
||||
|
||||
if (keyCounts?.signedCurve25519 != null) {
|
||||
|
@ -306,7 +240,7 @@ internal class OlmMachine(
|
|||
val devices =
|
||||
DeviceLists(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
|
||||
val adapter =
|
||||
MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
|
||||
moshi.adapter(ToDeviceSyncResponse::class.java)
|
||||
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
|
||||
|
||||
// TODO once our sync response type parses the unused fallback key
|
||||
|
@ -315,11 +249,17 @@ internal class OlmMachine(
|
|||
}
|
||||
|
||||
// We may get cross signing keys over a to-device event, update our listeners.
|
||||
this.updateLivePrivateKeys()
|
||||
updateLivePrivateKeys()
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) {
|
||||
val adapter = moshi.adapter(Event::class.java)
|
||||
val serializedEvent = adapter.toJson(event)
|
||||
inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given list of users to be tracked, triggering a key query request for them.
|
||||
*
|
||||
|
@ -329,7 +269,7 @@ internal class OlmMachine(
|
|||
* @param users The users that should be queued up for a key query.
|
||||
*/
|
||||
suspend fun updateTrackedUsers(users: List<String>) =
|
||||
withContext(Dispatchers.IO) { inner.updateTrackedUsers(users) }
|
||||
withContext(coroutineDispatchers.io) { inner.updateTrackedUsers(users) }
|
||||
|
||||
/**
|
||||
* Check if the given user is considered to be tracked.
|
||||
|
@ -338,7 +278,7 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
fun isUserTracked(userId: String): Boolean {
|
||||
return this.inner.isUserTracked(userId)
|
||||
return inner.isUserTracked(userId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -355,7 +295,7 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun getMissingSessions(users: List<String>): Request? =
|
||||
withContext(Dispatchers.IO) { inner.getMissingSessions(users) }
|
||||
withContext(coroutineDispatchers.io) { inner.getMissingSessions(users) }
|
||||
|
||||
/**
|
||||
* Share a room key with the given list of users for the given room.
|
||||
|
@ -377,7 +317,7 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
|
||||
withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) }
|
||||
withContext(coroutineDispatchers.io) { inner.shareRoomKey(roomId, users) }
|
||||
|
||||
/**
|
||||
* Encrypt the given event with the given type and content for the given room.
|
||||
|
@ -411,8 +351,8 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
|
||||
withContext(Dispatchers.IO) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val adapter = moshi.adapter<Content>(Map::class.java)
|
||||
val contentString = adapter.toJson(content)
|
||||
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
||||
adapter.fromJson(encrypted)!!
|
||||
|
@ -429,17 +369,17 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(MXCryptoError::class)
|
||||
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
||||
withContext(Dispatchers.IO) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||
val serializedEvent = adapter.toJson(event)
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val adapter = moshi.adapter(Event::class.java)
|
||||
try {
|
||||
if (event.roomId.isNullOrBlank()) {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
}
|
||||
val serializedEvent = adapter.toJson(event)
|
||||
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId)
|
||||
|
||||
val deserializationAdapter =
|
||||
MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java)
|
||||
moshi.adapter<JsonDict>(Map::class.java)
|
||||
val clearEvent = deserializationAdapter.fromJson(decrypted.clearEvent)
|
||||
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
|
||||
|
@ -469,8 +409,8 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(DecryptionException::class)
|
||||
suspend fun requestRoomKey(event: Event): KeyRequestPair =
|
||||
withContext(Dispatchers.IO) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val adapter = moshi.adapter(Event::class.java)
|
||||
val serializedEvent = adapter.toJson(event)
|
||||
|
||||
inner.requestRoomKey(serializedEvent, event.roomId!!)
|
||||
|
@ -488,7 +428,7 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray =
|
||||
withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() }
|
||||
withContext(coroutineDispatchers.io) { inner.exportKeys(passphrase, rounds).toByteArray() }
|
||||
|
||||
/**
|
||||
* Import room keys from the given serialized key export.
|
||||
|
@ -505,7 +445,7 @@ internal class OlmMachine(
|
|||
passphrase: String,
|
||||
listener: ProgressListener?
|
||||
): ImportRoomKeysResult =
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val decodedKeys = String(keys, Charset.defaultCharset())
|
||||
|
||||
val rustListener = CryptoProgressListener(listener)
|
||||
|
@ -520,8 +460,8 @@ internal class OlmMachine(
|
|||
keys: List<MegolmSessionData>,
|
||||
listener: ProgressListener?
|
||||
): ImportRoomKeysResult =
|
||||
withContext(Dispatchers.IO) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(List::class.java)
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val adapter = moshi.adapter(List::class.java)
|
||||
|
||||
// If the key backup is too big we take the risk of causing OOM
|
||||
// when serializing to json
|
||||
|
@ -550,25 +490,31 @@ internal class OlmMachine(
|
|||
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun getIdentity(userId: String): UserIdentities? {
|
||||
val identity = withContext(Dispatchers.IO) {
|
||||
val identity = withContext(coroutineDispatchers.io) {
|
||||
inner.getIdentity(userId)
|
||||
}
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
|
||||
val adapter = moshi.adapter(RestKeyInfo::class.java)
|
||||
|
||||
return when (identity) {
|
||||
is RustUserIdentity.Other -> {
|
||||
val verified = this.inner().isIdentityVerified(userId)
|
||||
val verified = inner().isIdentityVerified(userId)
|
||||
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
|
||||
trustLevel = DeviceTrustLevel(verified, verified)
|
||||
}
|
||||
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel().apply {
|
||||
trustLevel = DeviceTrustLevel(verified, verified)
|
||||
}
|
||||
|
||||
UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender)
|
||||
UserIdentity(
|
||||
userId = identity.userId,
|
||||
masterKey = masterKey,
|
||||
selfSigningKey = selfSigningKey,
|
||||
olmMachine = this,
|
||||
requestSender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers
|
||||
)
|
||||
}
|
||||
is RustUserIdentity.Own -> {
|
||||
val verified = this.inner().isIdentityVerified(userId)
|
||||
val verified = inner().isIdentityVerified(userId)
|
||||
|
||||
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
|
||||
trustLevel = DeviceTrustLevel(verified, verified)
|
||||
|
@ -579,13 +525,14 @@ internal class OlmMachine(
|
|||
val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel()
|
||||
|
||||
OwnUserIdentity(
|
||||
identity.userId,
|
||||
masterKey,
|
||||
selfSigningKey,
|
||||
userSigningKey,
|
||||
identity.trustsOurOwnDevice,
|
||||
this,
|
||||
this.requestSender
|
||||
userId = identity.userId,
|
||||
masterKey = masterKey,
|
||||
selfSigningKey = selfSigningKey,
|
||||
userSigningKey = userSigningKey,
|
||||
trustsOurOwnDevice = identity.trustsOurOwnDevice,
|
||||
olmMachine = this,
|
||||
requestSender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers
|
||||
)
|
||||
}
|
||||
null -> null
|
||||
|
@ -605,27 +552,35 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? {
|
||||
return if (userId == userId() && deviceId == deviceId()) {
|
||||
// Our own device isn't part of our store on the Rust side, return it
|
||||
// using our ownDevice method
|
||||
ownDevice()
|
||||
} else {
|
||||
getDevice(userId, deviceId)?.toCryptoDeviceInfo()
|
||||
}
|
||||
return getDevice(userId, deviceId)?.toCryptoDeviceInfo()
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun getDevice(userId: String, deviceId: String): Device? {
|
||||
val device = withContext(Dispatchers.IO) {
|
||||
val device = withContext(coroutineDispatchers.io) {
|
||||
inner.getDevice(userId, deviceId)
|
||||
} ?: return null
|
||||
|
||||
return Device(this.inner, device, this.requestSender, this.verificationListeners)
|
||||
return Device(
|
||||
machine = inner,
|
||||
inner = device,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getUserDevices(userId: String): List<Device> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
inner.getUserDevices(userId).map { Device(inner, it, requestSender, verificationListeners) }
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
inner.getUserDevices(userId).map {
|
||||
Device(
|
||||
machine = inner,
|
||||
inner = it,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -638,15 +593,17 @@ internal class OlmMachine(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
|
||||
|
||||
return getUserDevices(userId).map { it.toCryptoDeviceInfo() }
|
||||
/*
|
||||
// EA doesn't differentiate much between our own and other devices of
|
||||
// while the rust-sdk does, append our own device here.
|
||||
if (userId == this.userId()) {
|
||||
devices.add(this.ownDevice())
|
||||
if (userId == userId()) {
|
||||
devices.add(ownDevice())
|
||||
}
|
||||
|
||||
return devices
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -660,7 +617,7 @@ internal class OlmMachine(
|
|||
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
||||
|
||||
for (user in userIds) {
|
||||
val devices = this.getCryptoDeviceInfo(user)
|
||||
val devices = getCryptoDeviceInfo(user)
|
||||
plainDevices.addAll(devices)
|
||||
}
|
||||
|
||||
|
@ -669,7 +626,7 @@ internal class OlmMachine(
|
|||
|
||||
@Throws
|
||||
suspend fun forceKeyDownload(userIds: List<String>) {
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
val response = requestSender.queryKeys(Request.KeysQuery(requestId, userIds))
|
||||
markRequestAsSent(requestId, RequestType.KEYS_QUERY, response)
|
||||
|
@ -680,7 +637,7 @@ internal class OlmMachine(
|
|||
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||
|
||||
for (user in userIds) {
|
||||
val devices = this.getCryptoDeviceInfo(user)
|
||||
val devices = getCryptoDeviceInfo(user)
|
||||
|
||||
for (device in devices) {
|
||||
userMap.setObject(user, device.deviceId, device)
|
||||
|
@ -697,7 +654,18 @@ internal class OlmMachine(
|
|||
* The key query request will be retried a few time in case of shaky connection, but could fail.
|
||||
*/
|
||||
suspend fun ensureUserDevicesMap(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||
val toDownload = if (forceDownload) {
|
||||
ensureUsersKeys(userIds, forceDownload)
|
||||
return getUserDevicesMap(userIds)
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is untracked or forceDownload is set to true, a key query request will be made.
|
||||
* It will suspend until query response.
|
||||
*
|
||||
* The key query request will be retried a few time in case of shaky connection, but could fail.
|
||||
*/
|
||||
suspend fun ensureUsersKeys(userIds: List<String>, forceDownload: Boolean = false) {
|
||||
val userIdsToFetchKeys = if (forceDownload) {
|
||||
userIds
|
||||
} else {
|
||||
userIds.mapNotNull { userId ->
|
||||
|
@ -706,26 +674,34 @@ internal class OlmMachine(
|
|||
updateTrackedUsers(it)
|
||||
}
|
||||
}
|
||||
tryOrNull("Failed to download keys for $toDownload") {
|
||||
forceKeyDownload(toDownload)
|
||||
tryOrNull("Failed to download keys for $userIdsToFetchKeys") {
|
||||
forceKeyDownload(userIdsToFetchKeys)
|
||||
}
|
||||
return getUserDevicesMap(userIds)
|
||||
}
|
||||
|
||||
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
||||
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver)
|
||||
liveIdentity.value = identity
|
||||
|
||||
return liveIdentity
|
||||
fun getLiveUserIdentity(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
||||
return channelFlow {
|
||||
val userIdentityCollector = UserIdentityCollector(userId, this)
|
||||
val onClose = safeInvokeOnClose {
|
||||
flowCollectors.userIdentityCollectors.remove(userIdentityCollector)
|
||||
}
|
||||
flowCollectors.userIdentityCollectors.add(userIdentityCollector)
|
||||
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
||||
send(identity)
|
||||
onClose.await()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
||||
val keys = this.exportCrossSigningKeys().toOptional()
|
||||
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver)
|
||||
liveKeys.value = keys
|
||||
|
||||
return liveKeys
|
||||
fun getLivePrivateCrossSigningKeys(): Flow<Optional<PrivateKeysInfo>> {
|
||||
return channelFlow {
|
||||
val onClose = safeInvokeOnClose {
|
||||
flowCollectors.privateKeyCollectors.remove(this)
|
||||
}
|
||||
flowCollectors.privateKeyCollectors.add(this)
|
||||
val keys = this@OlmMachine.exportCrossSigningKeys().toOptional()
|
||||
send(keys)
|
||||
onClose.await()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -736,14 +712,19 @@ internal class OlmMachine(
|
|||
*
|
||||
* @param userIds The ids of the device owners.
|
||||
*
|
||||
* @return The list of Devices or an empty list if there aren't any.
|
||||
* @return The list of Devices or an empty list if there aren't any as a Flow.
|
||||
*/
|
||||
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||
val plainDevices = getCryptoDeviceInfo(userIds)
|
||||
val devices = LiveDevice(userIds, deviceUpdateObserver)
|
||||
devices.value = plainDevices
|
||||
|
||||
return devices
|
||||
fun getLiveDevices(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
|
||||
return channelFlow {
|
||||
val devicesCollector = DevicesCollector(userIds, this)
|
||||
val onClose = safeInvokeOnClose {
|
||||
flowCollectors.deviceCollectors.remove(devicesCollector)
|
||||
}
|
||||
flowCollectors.deviceCollectors.add(devicesCollector)
|
||||
val devices = getCryptoDeviceInfo(userIds)
|
||||
send(devices)
|
||||
onClose.await()
|
||||
}
|
||||
}
|
||||
|
||||
/** Discard the currently active room key for the given room if there is one. */
|
||||
|
@ -760,26 +741,27 @@ internal class OlmMachine(
|
|||
* @return The list of [VerificationRequest] that we share with the given user
|
||||
*/
|
||||
fun getVerificationRequests(userId: String): List<VerificationRequest> {
|
||||
return this.inner.getVerificationRequests(userId).map {
|
||||
return inner.getVerificationRequests(userId).map {
|
||||
VerificationRequest(
|
||||
this.inner,
|
||||
it,
|
||||
this.requestSender,
|
||||
this.verificationListeners,
|
||||
machine = inner,
|
||||
inner = it,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a verification request for the given user with the given flow ID */
|
||||
fun getVerificationRequest(userId: String, flowId: String): VerificationRequest? {
|
||||
val request = this.inner.getVerificationRequest(userId, flowId)
|
||||
|
||||
val request = inner.getVerificationRequest(userId, flowId)
|
||||
return if (request != null) {
|
||||
VerificationRequest(
|
||||
this.inner,
|
||||
request,
|
||||
requestSender,
|
||||
this.verificationListeners,
|
||||
machine = inner,
|
||||
inner = request,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
@ -792,13 +774,26 @@ internal class OlmMachine(
|
|||
* verification.
|
||||
*/
|
||||
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
|
||||
return when (val verification = this.inner.getVerification(userId, flowId)) {
|
||||
return when (val verification = inner.getVerification(userId, flowId)) {
|
||||
is uniffi.olm.Verification.QrCodeV1 -> {
|
||||
val request = this.getVerificationRequest(userId, flowId) ?: return null
|
||||
QrCodeVerification(inner, request, verification.qrcode, requestSender, verificationListeners)
|
||||
val request = getVerificationRequest(userId, flowId) ?: return null
|
||||
QrCodeVerification(
|
||||
machine = inner,
|
||||
request = request,
|
||||
inner = verification.qrcode,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners
|
||||
)
|
||||
}
|
||||
is uniffi.olm.Verification.SasV1 -> {
|
||||
SasVerification(inner, verification.sas, requestSender, verificationListeners)
|
||||
SasVerification(
|
||||
machine = inner,
|
||||
inner = verification.sas,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners
|
||||
)
|
||||
}
|
||||
null -> {
|
||||
// This branch exists because scanning a QR code is tied to the QrCodeVerification,
|
||||
|
@ -808,7 +803,14 @@ internal class OlmMachine(
|
|||
val request = getVerificationRequest(userId, flowId) ?: return null
|
||||
|
||||
if (request.canScanQrCodes()) {
|
||||
QrCodeVerification(inner, request, null, requestSender, verificationListeners)
|
||||
QrCodeVerification(
|
||||
machine = inner,
|
||||
request = request,
|
||||
inner = null,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = verificationListeners
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -817,23 +819,22 @@ internal class OlmMachine(
|
|||
}
|
||||
|
||||
suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||
val requests = withContext(Dispatchers.IO) {
|
||||
val requests = withContext(coroutineDispatchers.io) {
|
||||
inner.bootstrapCrossSigning()
|
||||
}
|
||||
|
||||
this.requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
|
||||
this.requestSender.sendSignatureUpload(requests.signatureRequest)
|
||||
requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
|
||||
requestSender.sendSignatureUpload(requests.signatureRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of our private cross signing keys, i.e. which private keys do we have stored locally.
|
||||
*/
|
||||
fun crossSigningStatus(): CrossSigningStatus {
|
||||
return this.inner.crossSigningStatus()
|
||||
return inner.crossSigningStatus()
|
||||
}
|
||||
|
||||
suspend fun exportCrossSigningKeys(): PrivateKeysInfo? {
|
||||
val export = withContext(Dispatchers.IO) {
|
||||
val export = withContext(coroutineDispatchers.io) {
|
||||
inner.exportCrossSigningKeys()
|
||||
} ?: return null
|
||||
|
||||
|
@ -844,7 +845,7 @@ internal class OlmMachine(
|
|||
val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user)
|
||||
|
||||
var result: UserTrustResult
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
result = try {
|
||||
inner.importCrossSigningKeys(rustExport)
|
||||
|
||||
|
@ -861,21 +862,21 @@ internal class OlmMachine(
|
|||
UserTrustResult.Failure(failure.localizedMessage)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
withContext(coroutineDispatchers.main) {
|
||||
this@OlmMachine.updateLivePrivateKeys()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun sign(message: String): Map<String, Map<String, String>> {
|
||||
return withContext(Dispatchers.Default) {
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
inner.sign(message)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun enableBackupV1(key: String, version: String) {
|
||||
return withContext(Dispatchers.Default) {
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
val backupKey = MegolmV1BackupKey(key, mapOf(), null, MXCRYPTO_ALGORITHM_MEGOLM_BACKUP)
|
||||
inner.enableBackupV1(backupKey, version)
|
||||
}
|
||||
|
@ -891,26 +892,29 @@ internal class OlmMachine(
|
|||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
fun roomKeyCounts(): RoomKeyCounts {
|
||||
// TODO convert this to a suspendable method
|
||||
return inner.roomKeyCounts()
|
||||
suspend fun roomKeyCounts(): RoomKeyCounts {
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
inner.roomKeyCounts()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
fun getBackupKeys(): BackupKeys? {
|
||||
// TODO this needs to be suspendable
|
||||
return inner.getBackupKeys()
|
||||
suspend fun getBackupKeys(): BackupKeys? {
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
inner.getBackupKeys()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
fun saveRecoveryKey(key: String?, version: String?) {
|
||||
// TODO convert this to a suspendable method
|
||||
inner.saveRecoveryKey(key, version)
|
||||
suspend fun saveRecoveryKey(key: String?, version: String?) {
|
||||
withContext(coroutineDispatchers.computation) {
|
||||
inner.saveRecoveryKey(key, version)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun backupRoomKeys(): Request? {
|
||||
return withContext(Dispatchers.Default) {
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
Timber.d("BACKUP CREATING REQUEST")
|
||||
val request = inner.backupRoomKeys()
|
||||
Timber.d("BACKUP CREATED REQUEST: $request")
|
||||
|
@ -920,9 +924,8 @@ internal class OlmMachine(
|
|||
|
||||
@Throws(CryptoStoreException::class)
|
||||
suspend fun checkAuthDataSignature(authData: MegolmBackupAuthData): Boolean {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val adapter = MoshiProvider
|
||||
.providesMoshi()
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
val adapter = moshi
|
||||
.newBuilder()
|
||||
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||
.build()
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
|
@ -28,10 +31,16 @@ internal class OlmMachineProvider @Inject constructor(
|
|||
@UserId private val userId: String,
|
||||
@DeviceId private val deviceId: String?,
|
||||
@SessionFilesDirectory private val dataDir: File,
|
||||
requestSender: RequestSender
|
||||
requestSender: RequestSender,
|
||||
coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
moshi: Moshi
|
||||
) {
|
||||
|
||||
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
||||
|
||||
var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, requestSender)
|
||||
var olmMachine: OlmMachine = OlmMachine(
|
||||
user_id = userId,
|
||||
device_id = deviceId!!,
|
||||
path = dataDir,
|
||||
requestSender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
moshi = moshi)
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
||||
import uniffi.olm.CryptoStoreException
|
||||
import uniffi.olm.OlmMachine
|
||||
|
@ -37,13 +37,15 @@ internal class QrCodeVerification(
|
|||
private var request: VerificationRequest,
|
||||
private var inner: QrCode?,
|
||||
private val sender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
listeners: ArrayList<VerificationService.Listener>
|
||||
) : QrCodeVerificationTransaction {
|
||||
|
||||
private val dispatcher = UpdateDispatcher(listeners)
|
||||
|
||||
private fun dispatchTxUpdated() {
|
||||
refreshData()
|
||||
this.dispatcher.dispatchTxUpdated(this)
|
||||
dispatcher.dispatchTxUpdated(this)
|
||||
}
|
||||
|
||||
/** Generate, if possible, data that should be encoded as a QR code for QR code verification.
|
||||
|
@ -60,27 +62,25 @@ internal class QrCodeVerification(
|
|||
*/
|
||||
override val qrCodeText: String?
|
||||
get() {
|
||||
val data = this.inner?.let { this.machine.generateQrCode(it.otherUserId, it.flowId) }
|
||||
val data = inner?.let { machine.generateQrCode(it.otherUserId, it.flowId) }
|
||||
|
||||
// TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64?
|
||||
return data?.fromBase64()?.toString(Charsets.ISO_8859_1)
|
||||
}
|
||||
|
||||
/** Pass the data from a scanned QR code into the QR code verification object */
|
||||
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
|
||||
runBlocking {
|
||||
request.scanQrCode(otherQrCodeText)
|
||||
}
|
||||
override suspend fun userHasScannedOtherQrCode(otherQrCodeText: String) {
|
||||
request.scanQrCode(otherQrCodeText)
|
||||
dispatchTxUpdated()
|
||||
}
|
||||
|
||||
/** Confirm that the other side has indeed scanned the QR code we presented */
|
||||
override fun otherUserScannedMyQrCode() {
|
||||
runBlocking { confirm() }
|
||||
override suspend fun otherUserScannedMyQrCode() {
|
||||
confirm()
|
||||
}
|
||||
|
||||
/** Cancel the QR code verification, denying that the other side has scanned the QR code */
|
||||
override fun otherUserDidNotScannedMyQrCode() {
|
||||
override suspend fun otherUserDidNotScannedMyQrCode() {
|
||||
// TODO Is this code correct here? The old code seems to do this
|
||||
cancelHelper(CancelCode.MismatchedKeys)
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ internal class QrCodeVerification(
|
|||
override var state: VerificationTxState
|
||||
get() {
|
||||
refreshData()
|
||||
val inner = this.inner
|
||||
val inner = inner
|
||||
val cancelInfo = inner?.cancelInfo
|
||||
|
||||
return if (inner != null) {
|
||||
|
@ -114,22 +114,22 @@ internal class QrCodeVerification(
|
|||
|
||||
/** Get the unique id of this verification */
|
||||
override val transactionId: String
|
||||
get() = this.request.flowId()
|
||||
get() = request.flowId()
|
||||
|
||||
/** Get the user id of the other user participating in this verification flow */
|
||||
override val otherUserId: String
|
||||
get() = this.request.otherUser()
|
||||
get() = request.otherUser()
|
||||
|
||||
/** Get the device id of the other user's device participating in this verification flow */
|
||||
override var otherDeviceId: String?
|
||||
get() = this.request.otherDeviceId()
|
||||
get() = request.otherDeviceId()
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
set(value) {
|
||||
}
|
||||
|
||||
/** Did the other side initiate this verification flow */
|
||||
override val isIncoming: Boolean
|
||||
get() = !this.request.weStarted()
|
||||
get() = !request.weStarted()
|
||||
|
||||
/** Cancel the verification flow
|
||||
*
|
||||
|
@ -140,7 +140,7 @@ internal class QrCodeVerification(
|
|||
*
|
||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||
* */
|
||||
override fun cancel() {
|
||||
override suspend fun cancel() {
|
||||
cancelHelper(CancelCode.User)
|
||||
}
|
||||
|
||||
|
@ -155,13 +155,13 @@ internal class QrCodeVerification(
|
|||
*
|
||||
* @param code The cancel code that should be given as the reason for the cancellation.
|
||||
* */
|
||||
override fun cancel(code: CancelCode) {
|
||||
override suspend fun cancel(code: CancelCode) {
|
||||
cancelHelper(code)
|
||||
}
|
||||
|
||||
/** Is this verification happening over to-device messages */
|
||||
override fun isToDeviceTransport(): Boolean {
|
||||
return this.request.roomId() == null
|
||||
return request.roomId() == null
|
||||
}
|
||||
|
||||
/** Confirm the QR code verification
|
||||
|
@ -174,36 +174,36 @@ internal class QrCodeVerification(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
private suspend fun confirm() {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val result = withContext(coroutineDispatchers.io) {
|
||||
machine.confirmVerification(request.otherUser(), request.flowId())
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
this.sender.sendVerificationRequest(result.request)
|
||||
dispatchTxUpdated()
|
||||
|
||||
val signatureRequest = result.signatureRequest
|
||||
|
||||
if (signatureRequest != null) {
|
||||
this.sender.sendSignatureUpload(signatureRequest)
|
||||
for (verificationRequest in result.requests) {
|
||||
sender.sendVerificationRequest(verificationRequest)
|
||||
}
|
||||
val signatureRequest = result.signatureRequest
|
||||
if (signatureRequest != null) {
|
||||
sender.sendSignatureUpload(signatureRequest)
|
||||
}
|
||||
dispatchTxUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelHelper(code: CancelCode) {
|
||||
val request = this.machine.cancelVerification(this.request.otherUser(), this.request.flowId(), code.value)
|
||||
private suspend fun cancelHelper(code: CancelCode) {
|
||||
val request = machine.cancelVerification(request.otherUser(), request.flowId(), code.value)
|
||||
|
||||
if (request != null) {
|
||||
runBlocking { sender.sendVerificationRequest(request) }
|
||||
sender.sendVerificationRequest(request)
|
||||
dispatchTxUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch fresh data from the Rust side for our verification flow */
|
||||
private fun refreshData() {
|
||||
when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) {
|
||||
when (val verification = machine.getVerification(request.otherUser(), request.flowId())) {
|
||||
is Verification.QrCodeV1 -> {
|
||||
this.inner = verification.qrcode
|
||||
inner = verification.qrcode
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
|
@ -31,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
|||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RustCrossSigningService @Inject constructor(
|
||||
|
@ -45,30 +41,30 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
/**
|
||||
* Is our own device signed by our own cross signing identity
|
||||
*/
|
||||
override fun isCrossSigningVerified(): Boolean {
|
||||
return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) {
|
||||
override suspend fun isCrossSigningVerified(): Boolean {
|
||||
return when (val identity = olmMachine.getIdentity(olmMachine.userId())) {
|
||||
is OwnUserIdentity -> identity.trustsOurOwnDevice()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isUserTrusted(otherUserId: String): Boolean {
|
||||
override suspend fun isUserTrusted(otherUserId: String): Boolean {
|
||||
// This seems to be used only in tests.
|
||||
return this.checkUserTrust(otherUserId).isVerified()
|
||||
return checkUserTrust(otherUserId).isVerified()
|
||||
}
|
||||
|
||||
/**
|
||||
* Will not force a download of the key, but will verify signatures trust chain.
|
||||
* Checks that my trusted user key has signed the other user UserKey
|
||||
*/
|
||||
override fun checkUserTrust(otherUserId: String): UserTrustResult {
|
||||
val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }
|
||||
override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
|
||||
val identity = olmMachine.getIdentity(olmMachine.userId())
|
||||
|
||||
// While UserTrustResult has many different states, they are by the callers
|
||||
// converted to a boolean value immediately, thus we don't need to support
|
||||
// all the values.
|
||||
return if (identity != null) {
|
||||
val verified = runBlocking { identity.verified() }
|
||||
val verified = identity.verified()
|
||||
|
||||
if (verified) {
|
||||
UserTrustResult.Success
|
||||
|
@ -84,8 +80,8 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
* Initialize cross signing for this user.
|
||||
* Users needs to enter credentials
|
||||
*/
|
||||
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
|
||||
runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) }
|
||||
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||
olmMachine.bootstrapCrossSigning(uiaInterceptor)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,26 +104,26 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
*
|
||||
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
|
||||
*/
|
||||
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() }
|
||||
override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo()
|
||||
}
|
||||
|
||||
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||
return runBlocking { olmMachine.getLiveUserIdentity(userId) }
|
||||
override fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
||||
return olmMachine.getLiveUserIdentity(userId)
|
||||
}
|
||||
|
||||
/** Get our own public cross signing keys */
|
||||
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||
override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||
return getUserCrossSigningKeys(olmMachine.userId())
|
||||
}
|
||||
|
||||
/** Get our own private cross signing keys */
|
||||
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||
return runBlocking { olmMachine.exportCrossSigningKeys() }
|
||||
override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||
return olmMachine.exportCrossSigningKeys()
|
||||
}
|
||||
|
||||
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
||||
return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() }
|
||||
override fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
|
||||
return olmMachine.getLivePrivateCrossSigningKeys()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,34 +132,32 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
* Returning true means that we have the private self-signing and user-signing keys at hand.
|
||||
*/
|
||||
override fun canCrossSign(): Boolean {
|
||||
val status = this.olmMachine.crossSigningStatus()
|
||||
val status = olmMachine.crossSigningStatus()
|
||||
|
||||
return status.hasSelfSigning && status.hasUserSigning
|
||||
}
|
||||
|
||||
override fun allPrivateKeysKnown(): Boolean {
|
||||
val status = this.olmMachine.crossSigningStatus()
|
||||
val status = olmMachine.crossSigningStatus()
|
||||
|
||||
return status.hasMaster && status.hasSelfSigning && status.hasUserSigning
|
||||
}
|
||||
|
||||
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
||||
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
||||
override suspend fun trustUser(otherUserId: String) {
|
||||
// This is only used in a test
|
||||
val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) }
|
||||
|
||||
val userIdentity = olmMachine.getIdentity(otherUserId)
|
||||
if (userIdentity != null) {
|
||||
runBlocking { userIdentity.verify() }
|
||||
callback.onSuccess(Unit)
|
||||
userIdentity.verify()
|
||||
} else {
|
||||
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
|
||||
throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark our own master key as trusted */
|
||||
override fun markMyMasterKeyAsTrusted() {
|
||||
override suspend fun markMyMasterKeyAsTrusted() {
|
||||
// This doesn't seem to be used?
|
||||
this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback())
|
||||
trustUser(olmMachine.userId())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,10 +165,8 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
*/
|
||||
override suspend fun trustDevice(deviceId: String) {
|
||||
val device = olmMachine.getDevice(olmMachine.userId(), deviceId)
|
||||
|
||||
return if (device != null) {
|
||||
if (device != null) {
|
||||
val verified = device.verify()
|
||||
|
||||
if (verified) {
|
||||
return
|
||||
} else {
|
||||
|
@ -192,15 +184,15 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
* using the self-signing key for our own devices or using the user-signing key and the master
|
||||
* key of another user.
|
||||
*/
|
||||
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||
val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) }
|
||||
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||
val device = olmMachine.getDevice(otherUserId, otherDeviceId)
|
||||
|
||||
return if (device != null) {
|
||||
// TODO i don't quite understand the semantics here and there are no docs for
|
||||
// DeviceTrustResult, what do the different result types mean exactly,
|
||||
// do you return success only if the Device is cross signing verified?
|
||||
// what about the local trust if it isn't? why is the local trust passed as an argument here?
|
||||
DeviceTrustResult.Success(runBlocking { device.trustLevel() })
|
||||
DeviceTrustResult.Success(device.trustLevel())
|
||||
} else {
|
||||
DeviceTrustResult.UnknownDevice(otherDeviceId)
|
||||
}
|
||||
|
@ -215,7 +207,7 @@ internal class RustCrossSigningService @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onSecretUSKGossip(uskPrivateKey: String) {
|
||||
// And this.
|
||||
// And
|
||||
}
|
||||
|
||||
override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
||||
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
|
||||
import uniffi.olm.CryptoStoreException
|
||||
|
@ -37,6 +37,7 @@ internal class SasVerification(
|
|||
private val machine: OlmMachine,
|
||||
private var inner: Sas,
|
||||
private val sender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
listeners: ArrayList<VerificationService.Listener>
|
||||
) :
|
||||
SasVerificationTransaction {
|
||||
|
@ -44,27 +45,27 @@ internal class SasVerification(
|
|||
|
||||
private fun dispatchTxUpdated() {
|
||||
refreshData()
|
||||
this.dispatcher.dispatchTxUpdated(this)
|
||||
dispatcher.dispatchTxUpdated(this)
|
||||
}
|
||||
|
||||
/** The user ID of the other user that is participating in this verification flow */
|
||||
override val otherUserId: String = this.inner.otherUserId
|
||||
override val otherUserId: String = inner.otherUserId
|
||||
|
||||
/** Get the device id of the other user's device participating in this verification flow */
|
||||
override var otherDeviceId: String?
|
||||
get() = this.inner.otherDeviceId
|
||||
get() = inner.otherDeviceId
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
set(value) {
|
||||
}
|
||||
|
||||
/** Did the other side initiate this verification flow */
|
||||
override val isIncoming: Boolean
|
||||
get() = !this.inner.weStarted
|
||||
get() = !inner.weStarted
|
||||
|
||||
override var state: VerificationTxState
|
||||
get() {
|
||||
refreshData()
|
||||
val cancelInfo = this.inner.cancelInfo
|
||||
val cancelInfo = inner.cancelInfo
|
||||
|
||||
return when {
|
||||
cancelInfo != null -> {
|
||||
|
@ -84,7 +85,7 @@ internal class SasVerification(
|
|||
|
||||
/** Get the unique id of this verification */
|
||||
override val transactionId: String
|
||||
get() = this.inner.flowId
|
||||
get() = inner.flowId
|
||||
|
||||
/** Cancel the verification flow
|
||||
*
|
||||
|
@ -95,8 +96,8 @@ internal class SasVerification(
|
|||
*
|
||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||
* */
|
||||
override fun cancel() {
|
||||
this.cancelHelper(CancelCode.User)
|
||||
override suspend fun cancel() {
|
||||
cancelHelper(CancelCode.User)
|
||||
}
|
||||
|
||||
/** Cancel the verification flow
|
||||
|
@ -110,8 +111,8 @@ internal class SasVerification(
|
|||
*
|
||||
* @param code The cancel code that should be given as the reason for the cancellation.
|
||||
* */
|
||||
override fun cancel(code: CancelCode) {
|
||||
this.cancelHelper(code)
|
||||
override suspend fun cancel(code: CancelCode) {
|
||||
cancelHelper(code)
|
||||
}
|
||||
|
||||
/** Cancel the verification flow
|
||||
|
@ -123,25 +124,17 @@ internal class SasVerification(
|
|||
*
|
||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||
*/
|
||||
override fun shortCodeDoesNotMatch() {
|
||||
this.cancelHelper(CancelCode.MismatchedSas)
|
||||
override suspend fun shortCodeDoesNotMatch() {
|
||||
cancelHelper(CancelCode.MismatchedSas)
|
||||
}
|
||||
|
||||
/** Is this verification happening over to-device messages */
|
||||
override fun isToDeviceTransport(): Boolean = this.inner.roomId == null
|
||||
|
||||
/** Does the verification flow support showing decimals as the short auth string */
|
||||
override fun supportsDecimal(): Boolean {
|
||||
// This is ignored anyways, throw it away?
|
||||
// The spec also mandates that devices support at least decimal and
|
||||
// the rust-sdk cancels if devices don't support it
|
||||
return true
|
||||
}
|
||||
override fun isToDeviceTransport(): Boolean = inner.roomId == null
|
||||
|
||||
/** Does the verification flow support showing emojis as the short auth string */
|
||||
override fun supportsEmoji(): Boolean {
|
||||
refreshData()
|
||||
return this.inner.supportsEmoji
|
||||
return inner.supportsEmoji
|
||||
}
|
||||
|
||||
/** Confirm that the short authentication code matches on both sides
|
||||
|
@ -153,8 +146,8 @@ internal class SasVerification(
|
|||
* This method is a noop if we're not yet in a presentable state, i.e. we didn't receive
|
||||
* a m.key.verification.key event from the other side or we're cancelled.
|
||||
*/
|
||||
override fun userHasVerifiedShortCode() {
|
||||
runBlocking { confirm() }
|
||||
override suspend fun userHasVerifiedShortCode() {
|
||||
confirm()
|
||||
}
|
||||
|
||||
/** Accept the verification flow, signaling the other side that we do want to verify
|
||||
|
@ -165,8 +158,8 @@ internal class SasVerification(
|
|||
* This method is a noop if we send the start event out or if the verification has already
|
||||
* been accepted.
|
||||
*/
|
||||
override fun acceptVerification() {
|
||||
runBlocking { accept() }
|
||||
override suspend fun acceptVerification() {
|
||||
accept()
|
||||
}
|
||||
|
||||
/** Get the decimal representation of the short auth string
|
||||
|
@ -176,7 +169,7 @@ internal class SasVerification(
|
|||
* in a presentable state.
|
||||
*/
|
||||
override fun getDecimalCodeRepresentation(): String {
|
||||
val decimals = this.machine.getDecimals(this.inner.otherUserId, this.inner.flowId)
|
||||
val decimals = machine.getDecimals(inner.otherUserId, inner.flowId)
|
||||
|
||||
return decimals?.joinToString(" ") ?: ""
|
||||
}
|
||||
|
@ -188,52 +181,51 @@ internal class SasVerification(
|
|||
* state.
|
||||
*/
|
||||
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
|
||||
val emojiIndex = this.machine.getEmojiIndex(this.inner.otherUserId, this.inner.flowId)
|
||||
val emojiIndex = machine.getEmojiIndex(inner.otherUserId, inner.flowId)
|
||||
|
||||
return emojiIndex?.map { getEmojiForCode(it) } ?: listOf()
|
||||
}
|
||||
|
||||
internal suspend fun accept() {
|
||||
val request = this.machine.acceptSasVerification(this.inner.otherUserId, inner.flowId)
|
||||
val request = machine.acceptSasVerification(inner.otherUserId, inner.flowId)
|
||||
|
||||
if (request != null) {
|
||||
this.sender.sendVerificationRequest(request)
|
||||
sender.sendVerificationRequest(request)
|
||||
dispatchTxUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreException::class)
|
||||
private suspend fun confirm() {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val result = withContext(coroutineDispatchers.io) {
|
||||
machine.confirmVerification(inner.otherUserId, inner.flowId)
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
this.sender.sendVerificationRequest(result.request)
|
||||
dispatchTxUpdated()
|
||||
|
||||
val signatureRequest = result.signatureRequest
|
||||
|
||||
if (signatureRequest != null) {
|
||||
this.sender.sendSignatureUpload(signatureRequest)
|
||||
for (verificationRequest in result.requests) {
|
||||
sender.sendVerificationRequest(verificationRequest)
|
||||
}
|
||||
val signatureRequest = result.signatureRequest
|
||||
if (signatureRequest != null) {
|
||||
sender.sendSignatureUpload(signatureRequest)
|
||||
}
|
||||
dispatchTxUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelHelper(code: CancelCode) {
|
||||
val request = this.machine.cancelVerification(this.inner.otherUserId, inner.flowId, code.value)
|
||||
private suspend fun cancelHelper(code: CancelCode) {
|
||||
val request = machine.cancelVerification(inner.otherUserId, inner.flowId, code.value)
|
||||
|
||||
if (request != null) {
|
||||
runBlocking { sender.sendVerificationRequest(request) }
|
||||
sender.sendVerificationRequest(request)
|
||||
dispatchTxUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch fresh data from the Rust side for our verification flow */
|
||||
private fun refreshData() {
|
||||
when (val verification = this.machine.getVerification(this.inner.otherUserId, this.inner.flowId)) {
|
||||
when (val verification = machine.getVerification(inner.otherUserId, inner.flowId)) {
|
||||
is Verification.SasV1 -> {
|
||||
this.inner = verification.sas
|
||||
inner = verification.sas
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||
import uniffi.olm.CryptoStoreException
|
||||
import uniffi.olm.SignatureException
|
||||
|
@ -65,7 +65,7 @@ sealed class UserIdentities {
|
|||
/**
|
||||
* Convert the identity into a MxCrossSigningInfo class.
|
||||
*/
|
||||
abstract fun toMxCrossSigningInfo(): MXCrossSigningInfo
|
||||
abstract suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,11 +80,12 @@ internal class OwnUserIdentity(
|
|||
private val userSigningKey: CryptoCrossSigningKey,
|
||||
private val trustsOurOwnDevice: Boolean,
|
||||
private val olmMachine: OlmMachine,
|
||||
private val requestSender: RequestSender) : UserIdentities() {
|
||||
private val requestSender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
|
||||
/**
|
||||
* Our own user id.
|
||||
*/
|
||||
override fun userId() = this.userId
|
||||
override fun userId() = userId
|
||||
|
||||
/**
|
||||
* Manually verify our user identity.
|
||||
|
@ -95,8 +96,8 @@ internal class OwnUserIdentity(
|
|||
*/
|
||||
@Throws(SignatureException::class)
|
||||
override suspend fun verify() {
|
||||
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
|
||||
this.requestSender.sendSignatureUpload(request)
|
||||
val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
|
||||
requestSender.sendSignatureUpload(request)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,13 +107,13 @@ internal class OwnUserIdentity(
|
|||
*/
|
||||
@Throws(CryptoStoreException::class)
|
||||
override suspend fun verified(): Boolean {
|
||||
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
|
||||
return withContext(coroutineDispatchers.io) { olmMachine.inner().isIdentityVerified(userId) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the identity trust our own device.
|
||||
*/
|
||||
fun trustsOurOwnDevice() = this.trustsOurOwnDevice
|
||||
fun trustsOurOwnDevice() = trustsOurOwnDevice
|
||||
|
||||
/**
|
||||
* Request an interactive verification to begin
|
||||
|
@ -133,32 +134,33 @@ internal class OwnUserIdentity(
|
|||
@Throws(CryptoStoreException::class)
|
||||
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
|
||||
val stringMethods = prepareMethods(methods)
|
||||
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
|
||||
this.requestSender.sendVerificationRequest(result!!.request)
|
||||
val result = olmMachine.inner().requestSelfVerification(stringMethods)
|
||||
requestSender.sendVerificationRequest(result!!.request)
|
||||
|
||||
return VerificationRequest(
|
||||
this.olmMachine.inner(),
|
||||
result.verification,
|
||||
this.requestSender,
|
||||
this.olmMachine.verificationListeners
|
||||
machine = olmMachine.inner(),
|
||||
inner = result.verification,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = olmMachine.verificationListeners
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the identity into a MxCrossSigningInfo class.
|
||||
*/
|
||||
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
||||
val masterKey = this.masterKey
|
||||
val selfSigningKey = this.selfSigningKey
|
||||
val userSigningKey = this.userSigningKey
|
||||
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
|
||||
override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
||||
val masterKey = masterKey
|
||||
val selfSigningKey = selfSigningKey
|
||||
val userSigningKey = userSigningKey
|
||||
val trustLevel = DeviceTrustLevel(verified(), false)
|
||||
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
|
||||
masterKey.trustLevel = trustLevel
|
||||
selfSigningKey.trustLevel = trustLevel
|
||||
userSigningKey.trustLevel = trustLevel
|
||||
|
||||
val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey)
|
||||
return MXCrossSigningInfo(this.userId, crossSigningKeys)
|
||||
return MXCrossSigningInfo(userId, crossSigningKeys)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,11 +174,12 @@ internal class UserIdentity(
|
|||
private val masterKey: CryptoCrossSigningKey,
|
||||
private val selfSigningKey: CryptoCrossSigningKey,
|
||||
private val olmMachine: OlmMachine,
|
||||
private val requestSender: RequestSender) : UserIdentities() {
|
||||
private val requestSender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
|
||||
/**
|
||||
* The unique ID of the user that this identity belongs to.
|
||||
*/
|
||||
override fun userId() = this.userId
|
||||
override fun userId() = userId
|
||||
|
||||
/**
|
||||
* Manually verify this user identity.
|
||||
|
@ -189,8 +192,8 @@ internal class UserIdentity(
|
|||
*/
|
||||
@Throws(SignatureException::class)
|
||||
override suspend fun verify() {
|
||||
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
|
||||
this.requestSender.sendSignatureUpload(request)
|
||||
val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
|
||||
requestSender.sendSignatureUpload(request)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,7 +202,7 @@ internal class UserIdentity(
|
|||
* @return True if the identity is considered to be verified and trusted, false otherwise.
|
||||
*/
|
||||
override suspend fun verified(): Boolean {
|
||||
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
|
||||
return withContext(coroutineDispatchers.io) { olmMachine.inner().isIdentityVerified(userId) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,32 +235,33 @@ internal class UserIdentity(
|
|||
transactionId: String
|
||||
): VerificationRequest {
|
||||
val stringMethods = prepareMethods(methods)
|
||||
val content = this.olmMachine.inner().verificationRequestContent(this.userId, stringMethods)!!
|
||||
val content = olmMachine.inner().verificationRequestContent(userId, stringMethods)!!
|
||||
|
||||
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId)
|
||||
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId).eventId
|
||||
|
||||
val innerRequest = this.olmMachine.inner().requestVerification(this.userId, roomId, eventID, stringMethods)!!
|
||||
val innerRequest = olmMachine.inner().requestVerification(userId, roomId, eventID, stringMethods)!!
|
||||
|
||||
return VerificationRequest(
|
||||
this.olmMachine.inner(),
|
||||
innerRequest,
|
||||
this.requestSender,
|
||||
this.olmMachine.verificationListeners
|
||||
machine = olmMachine.inner(),
|
||||
inner = innerRequest,
|
||||
sender = requestSender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = olmMachine.verificationListeners
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the identity into a MxCrossSigningInfo class.
|
||||
*/
|
||||
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
||||
// val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey)
|
||||
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
|
||||
override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
||||
// val crossSigningKeys = listOf(masterKey, selfSigningKey)
|
||||
val trustLevel = DeviceTrustLevel(verified(), false)
|
||||
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
|
||||
masterKey.trustLevel = trustLevel
|
||||
selfSigningKey.trustLevel = trustLevel
|
||||
return MXCrossSigningInfo(this.userId, listOf(
|
||||
this.masterKey.also { it.trustLevel = trustLevel },
|
||||
this.selfSigningKey.also { it.trustLevel = trustLevel }
|
||||
return MXCrossSigningInfo(userId, listOf(
|
||||
masterKey.also { it.trustLevel = trustLevel },
|
||||
selfSigningKey.also { it.trustLevel = trustLevel }
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ package org.matrix.android.sdk.internal.crypto
|
|||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
||||
|
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
|
|||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||
import timber.log.Timber
|
||||
import uniffi.olm.OlmMachine
|
||||
|
@ -45,6 +46,7 @@ internal class VerificationRequest(
|
|||
private val machine: OlmMachine,
|
||||
private var inner: VerificationRequest,
|
||||
private val sender: RequestSender,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val listeners: ArrayList<VerificationService.Listener>
|
||||
) {
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
@ -53,7 +55,7 @@ internal class VerificationRequest(
|
|||
uiHandler.post {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.verificationRequestUpdated(this.toPendingVerificationRequest())
|
||||
it.verificationRequestUpdated(toPendingVerificationRequest())
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## Error while notifying listeners")
|
||||
}
|
||||
|
@ -68,12 +70,12 @@ internal class VerificationRequest(
|
|||
* event that initiated the flow.
|
||||
*/
|
||||
internal fun flowId(): String {
|
||||
return this.inner.flowId
|
||||
return inner.flowId
|
||||
}
|
||||
|
||||
/** The user ID of the other user that is participating in this verification flow */
|
||||
internal fun otherUser(): String {
|
||||
return this.inner.otherUserId
|
||||
return inner.otherUserId
|
||||
}
|
||||
|
||||
/** The device ID of the other user's device that is participating in this verification flow
|
||||
|
@ -83,12 +85,12 @@ internal class VerificationRequest(
|
|||
* */
|
||||
internal fun otherDeviceId(): String? {
|
||||
refreshData()
|
||||
return this.inner.otherDeviceId
|
||||
return inner.otherDeviceId
|
||||
}
|
||||
|
||||
/** Did we initiate this verification flow */
|
||||
internal fun weStarted(): Boolean {
|
||||
return this.inner.weStarted
|
||||
return inner.weStarted
|
||||
}
|
||||
|
||||
/** Get the id of the room where this verification is happening
|
||||
|
@ -96,7 +98,7 @@ internal class VerificationRequest(
|
|||
* Will be null if the verification is not happening inside a room.
|
||||
*/
|
||||
internal fun roomId(): String? {
|
||||
return this.inner.roomId
|
||||
return inner.roomId
|
||||
}
|
||||
|
||||
/** Did the non-initiating side respond with a m.key.verification.read event
|
||||
|
@ -107,13 +109,13 @@ internal class VerificationRequest(
|
|||
*/
|
||||
internal fun isReady(): Boolean {
|
||||
refreshData()
|
||||
return this.inner.isReady
|
||||
return inner.isReady
|
||||
}
|
||||
|
||||
/** Did we advertise that we're able to scan QR codes */
|
||||
internal fun canScanQrCodes(): Boolean {
|
||||
refreshData()
|
||||
return this.inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
|
||||
return inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
|
||||
}
|
||||
|
||||
/** Accept the verification request advertising the given methods as supported
|
||||
|
@ -132,15 +134,15 @@ internal class VerificationRequest(
|
|||
suspend fun acceptWithMethods(methods: List<VerificationMethod>) {
|
||||
val stringMethods = prepareMethods(methods)
|
||||
|
||||
val request = this.machine.acceptVerificationRequest(
|
||||
this.inner.otherUserId,
|
||||
this.inner.flowId,
|
||||
val request = machine.acceptVerificationRequest(
|
||||
inner.otherUserId,
|
||||
inner.flowId,
|
||||
stringMethods
|
||||
)
|
||||
|
||||
if (request != null) {
|
||||
this.sender.sendVerificationRequest(request)
|
||||
this.dispatchRequestUpdated()
|
||||
sender.sendVerificationRequest(request)
|
||||
dispatchRequestUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,12 +160,12 @@ internal class VerificationRequest(
|
|||
* emoji verification, or null if we can't yet transition into emoji verification.
|
||||
*/
|
||||
internal suspend fun startSasVerification(): SasVerification? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
val result = machine.startSasVerification(inner.otherUserId, inner.flowId)
|
||||
|
||||
if (result != null) {
|
||||
sender.sendVerificationRequest(result.request)
|
||||
SasVerification(machine, result.sas, sender, listeners)
|
||||
SasVerification(machine, result.sas, sender, coroutineDispatchers, listeners)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -187,10 +189,10 @@ internal class VerificationRequest(
|
|||
// TODO again, what's the deal with ISO_8859_1?
|
||||
val byteArray = data.toByteArray(Charsets.ISO_8859_1)
|
||||
val encodedData = byteArray.toBase64NoPadding()
|
||||
val result = this.machine.scanQrCode(this.otherUser(), this.flowId(), encodedData) ?: return null
|
||||
val result = machine.scanQrCode(otherUser(), flowId(), encodedData) ?: return null
|
||||
|
||||
this.sender.sendVerificationRequest(result.request)
|
||||
return QrCodeVerification(this.machine, this, result.qr, this.sender, this.listeners)
|
||||
sender.sendVerificationRequest(result.request)
|
||||
return QrCodeVerification(machine, this, result.qr, sender, coroutineDispatchers, listeners)
|
||||
}
|
||||
|
||||
/** Transition into a QR code verification to display a QR code
|
||||
|
@ -211,15 +213,16 @@ internal class VerificationRequest(
|
|||
* QR code verification, or null if we can't yet transition into QR code verification.
|
||||
*/
|
||||
internal fun startQrVerification(): QrCodeVerification? {
|
||||
val qrcode = this.machine.startQrVerification(this.inner.otherUserId, this.inner.flowId)
|
||||
val qrcode = machine.startQrVerification(inner.otherUserId, inner.flowId)
|
||||
|
||||
return if (qrcode != null) {
|
||||
QrCodeVerification(
|
||||
this.machine,
|
||||
this,
|
||||
qrcode,
|
||||
this.sender,
|
||||
this.listeners,
|
||||
machine = machine,
|
||||
request = this,
|
||||
inner = qrcode,
|
||||
sender = sender,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
listeners = listeners,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
@ -237,24 +240,24 @@ internal class VerificationRequest(
|
|||
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||
*/
|
||||
internal suspend fun cancel() {
|
||||
val request = this.machine.cancelVerification(
|
||||
this.inner.otherUserId,
|
||||
this.inner.flowId,
|
||||
val request = machine.cancelVerification(
|
||||
inner.otherUserId,
|
||||
inner.flowId,
|
||||
CancelCode.User.value
|
||||
)
|
||||
|
||||
if (request != null) {
|
||||
this.sender.sendVerificationRequest(request)
|
||||
this.dispatchRequestUpdated()
|
||||
sender.sendVerificationRequest(request)
|
||||
dispatchRequestUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch fresh data from the Rust side for our verification flow */
|
||||
private fun refreshData() {
|
||||
val request = this.machine.getVerificationRequest(this.inner.otherUserId, this.inner.flowId)
|
||||
val request = machine.getVerificationRequest(inner.otherUserId, inner.flowId)
|
||||
|
||||
if (request != null) {
|
||||
this.inner = request
|
||||
inner = request
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +272,7 @@ internal class VerificationRequest(
|
|||
*/
|
||||
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
|
||||
refreshData()
|
||||
val cancelInfo = this.inner.cancelInfo
|
||||
val cancelInfo = inner.cancelInfo
|
||||
val cancelCode =
|
||||
if (cancelInfo != null) {
|
||||
safeValueOf(cancelInfo.cancelCode)
|
||||
|
@ -277,72 +280,72 @@ internal class VerificationRequest(
|
|||
null
|
||||
}
|
||||
|
||||
val ourMethods = this.inner.ourMethods
|
||||
val theirMethods = this.inner.theirMethods
|
||||
val otherDeviceId = this.inner.otherDeviceId
|
||||
val ourMethods = inner.ourMethods
|
||||
val theirMethods = inner.theirMethods
|
||||
val otherDeviceId = inner.otherDeviceId
|
||||
|
||||
var requestInfo: ValidVerificationInfoRequest? = null
|
||||
var readyInfo: ValidVerificationInfoReady? = null
|
||||
|
||||
if (this.inner.weStarted && ourMethods != null) {
|
||||
if (inner.weStarted && ourMethods != null) {
|
||||
requestInfo =
|
||||
ValidVerificationInfoRequest(
|
||||
this.inner.flowId,
|
||||
this.machine.deviceId(),
|
||||
ourMethods,
|
||||
null,
|
||||
transactionId = inner.flowId,
|
||||
fromDevice = machine.deviceId(),
|
||||
methods = ourMethods,
|
||||
timestamp = null,
|
||||
)
|
||||
} else if (!this.inner.weStarted && ourMethods != null) {
|
||||
} else if (!inner.weStarted && ourMethods != null) {
|
||||
readyInfo =
|
||||
ValidVerificationInfoReady(
|
||||
this.inner.flowId,
|
||||
this.machine.deviceId(),
|
||||
ourMethods,
|
||||
transactionId = inner.flowId,
|
||||
fromDevice = machine.deviceId(),
|
||||
methods = ourMethods,
|
||||
)
|
||||
}
|
||||
|
||||
if (this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||
if (inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||
readyInfo =
|
||||
ValidVerificationInfoReady(
|
||||
this.inner.flowId,
|
||||
otherDeviceId,
|
||||
theirMethods,
|
||||
transactionId = inner.flowId,
|
||||
fromDevice = otherDeviceId,
|
||||
methods = theirMethods,
|
||||
)
|
||||
} else if (!this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||
} else if (!inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||
requestInfo =
|
||||
ValidVerificationInfoRequest(
|
||||
this.inner.flowId,
|
||||
otherDeviceId,
|
||||
theirMethods,
|
||||
System.currentTimeMillis(),
|
||||
transactionId = inner.flowId,
|
||||
fromDevice = otherDeviceId,
|
||||
methods = theirMethods,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
|
||||
return PendingVerificationRequest(
|
||||
// Creation time
|
||||
System.currentTimeMillis(),
|
||||
ageLocalTs = System.currentTimeMillis(),
|
||||
// Who initiated the request
|
||||
!this.inner.weStarted,
|
||||
isIncoming = !inner.weStarted,
|
||||
// Local echo id, what to do here?
|
||||
this.inner.flowId,
|
||||
localId = inner.flowId,
|
||||
// other user
|
||||
this.inner.otherUserId,
|
||||
otherUserId = inner.otherUserId,
|
||||
// room id
|
||||
this.inner.roomId,
|
||||
roomId = inner.roomId,
|
||||
// transaction id
|
||||
this.inner.flowId,
|
||||
transactionId = inner.flowId,
|
||||
// val requestInfo: ValidVerificationInfoRequest? = null,
|
||||
requestInfo,
|
||||
requestInfo = requestInfo,
|
||||
// val readyInfo: ValidVerificationInfoReady? = null,
|
||||
readyInfo,
|
||||
readyInfo = readyInfo,
|
||||
// cancel code if there is one
|
||||
cancelCode,
|
||||
cancelConclusion = cancelCode,
|
||||
// are we done/successful
|
||||
this.inner.isDone,
|
||||
isSuccessful = inner.isDone,
|
||||
// did another device answer the request
|
||||
this.inner.isPassive,
|
||||
handledByOtherSession = inner.isPassive,
|
||||
// devices that should receive the events we send out
|
||||
null,
|
||||
targetDevices = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Looper
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.WorkerThread
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
@ -41,7 +40,6 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
|||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager
|
||||
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
|
||||
import org.matrix.android.sdk.internal.crypto.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
|
@ -53,6 +51,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
|||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
|
@ -95,7 +94,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
|
||||
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
||||
|
||||
private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private val importScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.main)
|
||||
|
||||
private var keysBackupStateListener: KeysBackupStateListener? = null
|
||||
|
||||
|
@ -237,7 +236,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun canRestoreKeys(): Boolean {
|
||||
override suspend fun canRestoreKeys(): Boolean {
|
||||
val keyCountOnServer = keysBackupVersion?.count ?: return false
|
||||
val keyCountLocally = getTotalNumbersOfKeys()
|
||||
|
||||
|
@ -246,11 +245,11 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
return keyCountLocally < keyCountOnServer
|
||||
}
|
||||
|
||||
override fun getTotalNumbersOfKeys(): Int {
|
||||
override suspend fun getTotalNumbersOfKeys(): Int {
|
||||
return olmMachine.roomKeyCounts().total.toInt()
|
||||
}
|
||||
|
||||
override fun getTotalNumbersOfBackedUpKeys(): Int {
|
||||
override suspend fun getTotalNumbersOfBackedUpKeys(): Int {
|
||||
return olmMachine.roomKeyCounts().backedUp.toInt()
|
||||
}
|
||||
|
||||
|
@ -405,7 +404,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getBackupProgress(progressListener: ProgressListener) {
|
||||
override suspend fun getBackupProgress(progressListener: ProgressListener) {
|
||||
val backedUpKeys = getTotalNumbersOfBackedUpKeys()
|
||||
val total = getTotalNumbersOfKeys()
|
||||
|
||||
|
@ -490,7 +489,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
||||
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
withContext(Dispatchers.Main) {
|
||||
withContext(coroutineDispatchers.main) {
|
||||
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
|
||||
}
|
||||
// Decrypting by chunk of 500 keys in parallel
|
||||
|
@ -513,7 +512,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
.awaitAll()
|
||||
.flatten()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
withContext(coroutineDispatchers.main) {
|
||||
val stepProgress = StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size)
|
||||
stepProgressListener?.onStepProgress(stepProgress)
|
||||
}
|
||||
|
@ -532,7 +531,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
val progressListener = if (stepProgressListener != null) {
|
||||
object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
cryptoCoroutineScope.launch(Dispatchers.Main) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||
val stepProgress = StepProgressListener.Step.ImportingKey(progress, total)
|
||||
stepProgressListener.onStepProgress(stepProgress)
|
||||
}
|
||||
|
@ -581,16 +580,12 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun getVersion(version: String): KeysVersionResult? {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
sender.getKeyBackupVersion(version)
|
||||
}
|
||||
return sender.getKeyBackupVersion(version)
|
||||
}
|
||||
|
||||
@Throws
|
||||
override suspend fun getCurrentVersion(): KeysVersionResult? {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
sender.getKeyBackupVersion()
|
||||
}
|
||||
return sender.getKeyBackupVersion()
|
||||
}
|
||||
|
||||
override suspend fun forceUsingLastVersion(): Boolean {
|
||||
|
@ -646,18 +641,16 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
Timber.w("checkAndStartKeysBackup: invalid state: $state")
|
||||
return@withContext
|
||||
}
|
||||
|
||||
keysBackupVersion = null
|
||||
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
|
||||
|
||||
withContext(coroutineDispatchers.io) {
|
||||
try {
|
||||
val data = getCurrentVersion()
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
checkAndStartWithKeysBackupVersion(data)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
||||
try {
|
||||
val data = getCurrentVersion()
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
checkAndStartWithKeysBackupVersion(data)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
keysBackupStateManager.state = KeysBackupState.Unknown
|
||||
}
|
||||
}
|
||||
|
@ -725,7 +718,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
|
||||
override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
|
||||
val info = olmMachine.getBackupKeys() ?: return null
|
||||
return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
|
||||
}
|
||||
|
@ -878,7 +871,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||
}
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is Failure.ServerError) {
|
||||
withContext(Dispatchers.Main) {
|
||||
withContext(coroutineDispatchers.main) {
|
||||
Timber.e(failure, "backupKeys: backupKeys failed.")
|
||||
|
||||
when (failure.error.code) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,8 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto
|
||||
package org.matrix.android.sdk.internal.crypto.network
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import dagger.Lazy
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
|
@ -46,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||
|
@ -56,6 +58,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
|
|||
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||
import timber.log.Timber
|
||||
import uniffi.olm.OutgoingVerificationRequest
|
||||
import uniffi.olm.Request
|
||||
|
@ -80,6 +83,7 @@ internal class RequestSender @Inject constructor(
|
|||
private val getSessionsDataTask: GetSessionsDataTask,
|
||||
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
|
||||
private val getRoomSessionDataTask: GetRoomSessionDataTask,
|
||||
private val moshi: Moshi
|
||||
) {
|
||||
companion object {
|
||||
const val REQUEST_RETRY_COUNT = 3
|
||||
|
@ -97,16 +101,16 @@ internal class RequestSender @Inject constructor(
|
|||
suspend fun queryKeys(request: Request.KeysQuery): String {
|
||||
val params = DownloadKeysForUsersTask.Params(request.users, null)
|
||||
val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(KeysQueryResponse::class.java)
|
||||
val adapter = moshi.adapter(KeysQueryResponse::class.java)
|
||||
return adapter.toJson(response)!!
|
||||
}
|
||||
|
||||
suspend fun uploadKeys(request: Request.KeysUpload): String {
|
||||
val body = MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java).fromJson(request.body)!!
|
||||
val body = moshi.adapter<JsonDict>(Map::class.java).fromJson(request.body)!!
|
||||
val params = UploadKeysTask.Params(body)
|
||||
|
||||
val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(KeysUploadResponse::class.java)
|
||||
val adapter = moshi.adapter(KeysUploadResponse::class.java)
|
||||
|
||||
return adapter.toJson(response)!!
|
||||
}
|
||||
|
@ -118,42 +122,46 @@ internal class RequestSender @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): String {
|
||||
private suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): SendResponse {
|
||||
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
||||
}
|
||||
|
||||
suspend fun sendRoomMessage(request: Request.RoomMessage): String {
|
||||
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
||||
val sendResponse = sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
||||
val responseAdapter = moshi.adapter(SendResponse::class.java)
|
||||
return responseAdapter.toJson(sendResponse)
|
||||
}
|
||||
|
||||
suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): String {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
||||
val jsonContent = adapter.fromJson(content)
|
||||
suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): SendResponse {
|
||||
val paramsAdapter = moshi.adapter<Content>(Map::class.java)
|
||||
val jsonContent = paramsAdapter.fromJson(content)
|
||||
val event = Event(eventType, transactionId, jsonContent, roomId = roomId)
|
||||
val params = SendVerificationMessageTask.Params(event)
|
||||
return this.sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
return sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
}
|
||||
|
||||
suspend fun sendSignatureUpload(request: Request.SignatureUpload) {
|
||||
sendSignatureUpload(request.body)
|
||||
suspend fun sendSignatureUpload(request: Request.SignatureUpload): String {
|
||||
return sendSignatureUpload(request.body)
|
||||
}
|
||||
|
||||
suspend fun sendSignatureUpload(request: SignatureUploadRequest) {
|
||||
sendSignatureUpload(request.body)
|
||||
suspend fun sendSignatureUpload(request: SignatureUploadRequest): String {
|
||||
return sendSignatureUpload(request.body)
|
||||
}
|
||||
|
||||
private suspend fun sendSignatureUpload(body: String) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<Map<String, Map<String, Any>>>(Map::class.java)
|
||||
val signatures = adapter.fromJson(body)!!
|
||||
private suspend fun sendSignatureUpload(body: String): String {
|
||||
val paramsAdapter = moshi.adapter<Map<String, Map<String, Any>>>(Map::class.java)
|
||||
val signatures = paramsAdapter.fromJson(body)!!
|
||||
val params = UploadSignaturesTask.Params(signatures)
|
||||
this.signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
val response = signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
val responseAdapter = moshi.adapter(SignatureUploadResponse::class.java)
|
||||
return responseAdapter.toJson(response)!!
|
||||
}
|
||||
|
||||
suspend fun uploadCrossSigningKeys(
|
||||
request: UploadSigningKeysRequest,
|
||||
interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
|
||||
) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
|
||||
val adapter = moshi.adapter(RestKeyInfo::class.java)
|
||||
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
|
||||
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
|
||||
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
|
||||
|
@ -195,8 +203,7 @@ internal class RequestSender @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun sendToDevice(eventType: String, body: String, transactionId: String) {
|
||||
val adapter = MoshiProvider
|
||||
.providesMoshi()
|
||||
val adapter = moshi
|
||||
.newBuilder()
|
||||
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||
.build()
|
||||
|
@ -252,7 +259,7 @@ internal class RequestSender @Inject constructor(
|
|||
val keys = adapter.fromJson(request.rooms)!!
|
||||
val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys))
|
||||
val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||
val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java)
|
||||
val responseAdapter = moshi.adapter(BackupKeysResult::class.java)
|
||||
return responseAdapter.toJson(response)!!
|
||||
}
|
||||
|
|
@ -22,18 +22,14 @@ import org.matrix.android.sdk.api.session.events.model.toContent
|
|||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
|
||||
data class Params(val roomId: String,
|
||||
val event: Event,
|
||||
/**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
|
||||
val keepKeys: List<String>? = null
|
||||
val event: Event
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -51,52 +47,34 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||
|
||||
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)
|
||||
|
||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||
params.keepKeys?.forEach {
|
||||
localMutableContent.remove(it)
|
||||
}
|
||||
|
||||
// try {
|
||||
// let it throws
|
||||
awaitCallback<MXEncryptEventContentResult> {
|
||||
cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
|
||||
}.let { result ->
|
||||
val modifiedContent = HashMap(result.eventContent)
|
||||
params.keepKeys?.forEach { toKeep ->
|
||||
localEvent.content?.get(toKeep)?.let {
|
||||
// put it back in the encrypted thing
|
||||
modifiedContent[toKeep] = it
|
||||
}
|
||||
}
|
||||
val safeResult = result.copy(eventContent = modifiedContent)
|
||||
// Better handling of local echo, to avoid decrypting transition on remote echo
|
||||
// Should I only do it for text messages?
|
||||
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
MXEventDecryptionResult(
|
||||
clearEvent = Event(
|
||||
type = localEvent.type,
|
||||
content = localEvent.content,
|
||||
roomId = localEvent.roomId
|
||||
).toContent(),
|
||||
forwardingCurve25519KeyChain = emptyList(),
|
||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||
claimedEd25519Key = cryptoService.getMyDevice().fingerprint()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
||||
localEcho.type = EventType.ENCRYPTED
|
||||
localEcho.content = ContentMapper.map(modifiedContent)
|
||||
decryptionLocalEcho?.also {
|
||||
localEcho.setDecryptionResult(it)
|
||||
}
|
||||
}
|
||||
return localEvent.copy(
|
||||
type = safeResult.eventType,
|
||||
content = safeResult.eventContent
|
||||
val result = cryptoService.encryptEventContent(localEvent.content ?: emptyMap(), localEvent.type, params.roomId)
|
||||
// Better handling of local echo, to avoid decrypting transition on remote echo
|
||||
// Should I only do it for text messages?
|
||||
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
MXEventDecryptionResult(
|
||||
clearEvent = Event(
|
||||
type = localEvent.type,
|
||||
content = localEvent.content,
|
||||
roomId = localEvent.roomId
|
||||
).toContent(),
|
||||
forwardingCurve25519KeyChain = emptyList(),
|
||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||
claimedEd25519Key = cryptoService.getMyCryptoDevice().fingerprint()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
||||
localEcho.type = EventType.ENCRYPTED
|
||||
localEcho.content = ContentMapper.map(result.eventContent)
|
||||
decryptionLocalEcho?.also {
|
||||
localEcho.setDecryptionResult(it)
|
||||
}
|
||||
}
|
||||
return localEvent.copy(
|
||||
type = result.eventType,
|
||||
content = result.eventContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,8 +76,7 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||
if (params.encrypt && !params.event.isEncrypted()) {
|
||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||
params.event.roomId ?: "",
|
||||
params.event,
|
||||
listOf("m.relates_to")
|
||||
params.event
|
||||
))
|
||||
}
|
||||
return params.event
|
||||
|
|
|
@ -22,11 +22,12 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
|||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.toMatrixErrorStr
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
|
||||
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
|
||||
data class Params(
|
||||
val event: Event
|
||||
)
|
||||
|
@ -39,10 +40,9 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
|
||||
|
||||
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
||||
override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse {
|
||||
val event = handleEncryption(params)
|
||||
val localId = event.eventId!!
|
||||
|
||||
try {
|
||||
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
|
||||
val response = executeRequest(globalErrorReceiver) {
|
||||
|
@ -54,7 +54,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
)
|
||||
}
|
||||
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
|
||||
return response.eventId
|
||||
return response
|
||||
} catch (e: Throwable) {
|
||||
localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr())
|
||||
throw e
|
||||
|
@ -67,7 +67,6 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||
params.event.roomId ?: "",
|
||||
params.event,
|
||||
listOf("m.relates_to")
|
||||
))
|
||||
} catch (throwable: Throwable) {
|
||||
// We said it's ok to send verification request in clear
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
*/
|
||||
package org.matrix.android.sdk.internal.crypto.tasks
|
||||
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, Unit> {
|
||||
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, SignatureUploadResponse> {
|
||||
data class Params(
|
||||
val signatures: Map<String, Map<String, Any>>
|
||||
)
|
||||
|
@ -33,21 +33,13 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
|
|||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : UploadSignaturesTask {
|
||||
|
||||
override suspend fun execute(params: UploadSignaturesTask.Params) {
|
||||
try {
|
||||
val response = executeRequest(
|
||||
globalErrorReceiver,
|
||||
canRetry = true,
|
||||
maxRetriesCount = 10
|
||||
) {
|
||||
cryptoApi.uploadSignatures(params.signatures)
|
||||
}
|
||||
if (response.failures?.isNotEmpty() == true) {
|
||||
throw Throwable(response.failures.toString())
|
||||
}
|
||||
return
|
||||
} catch (f: Failure) {
|
||||
throw f
|
||||
override suspend fun execute(params: UploadSignaturesTask.Params): SignatureUploadResponse {
|
||||
return executeRequest(
|
||||
globalErrorReceiver,
|
||||
canRetry = true,
|
||||
maxRetriesCount = 10
|
||||
) {
|
||||
cryptoApi.uploadSignatures(params.signatures)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.verification
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
|
@ -26,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
|
|||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
|
||||
|
@ -77,31 +77,41 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
olmMachineProvider.olmMachine
|
||||
}
|
||||
|
||||
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
|
||||
private val dispatcher = UpdateDispatcher(olmMachine.verificationListeners)
|
||||
|
||||
/** The main entry point for the verification service
|
||||
/**
|
||||
*
|
||||
* All verification related events should be forwarded through this method to
|
||||
* the verification service.
|
||||
*
|
||||
* Since events are at this point already handled by the rust-sdk through the receival
|
||||
* of the to-device events and the decryption of room events, this method mainly just
|
||||
* If the verification event is not encrypted it should be provided to the olmMachine.
|
||||
* Otherwise events are at this point already handled by the rust-sdk through the receival
|
||||
* of the to-device events and the decryption of room events. In this case this method mainly just
|
||||
* fetches the appropriate rust object that will be created or updated by the event and
|
||||
* dispatches updates to our listeners.
|
||||
*/
|
||||
internal suspend fun onEvent(event: Event) = when (event.getClearType()) {
|
||||
// I'm not entirely sure why getClearType() returns a msgtype in one case
|
||||
// and a event type in the other case, but this is how the old verification
|
||||
// service did things and it does seem to work.
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> onRequest(event)
|
||||
EventType.KEY_VERIFICATION_START -> onStart(event)
|
||||
EventType.KEY_VERIFICATION_READY,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_DONE -> onUpdate(event)
|
||||
else -> {
|
||||
internal suspend fun onEvent(roomId: String?, event: Event) {
|
||||
if (roomId != null && !event.isEncrypted()) {
|
||||
olmMachine.receiveUnencryptedVerificationEvent(roomId, event)
|
||||
}
|
||||
when (event.getClearType()) {
|
||||
EventType.KEY_VERIFICATION_REQUEST -> onRequest(event, fromRoomMessage = false)
|
||||
EventType.KEY_VERIFICATION_START -> onStart(event)
|
||||
EventType.KEY_VERIFICATION_READY,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_DONE -> onUpdate(event)
|
||||
EventType.MESSAGE -> onRoomMessage(event)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRoomMessage(event: Event) {
|
||||
val messageContent = event.getClearContent()?.toModel<MessageContent>() ?: return
|
||||
if (messageContent.msgType == MessageType.MSGTYPE_VERIFICATION_REQUEST) {
|
||||
onRequest(event, fromRoomMessage = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,9 +120,9 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
val sender = event.senderId ?: return
|
||||
val flowId = getFlowId(event) ?: return
|
||||
|
||||
this.olmMachine.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated()
|
||||
val verification = this.getExistingTransaction(sender, flowId) ?: return
|
||||
this.dispatcher.dispatchTxUpdated(verification)
|
||||
olmMachine.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated()
|
||||
val verification = getExistingTransaction(sender, flowId) ?: return
|
||||
dispatcher.dispatchTxUpdated(verification)
|
||||
}
|
||||
|
||||
/** Check if the start event created new verification objects and dispatch updates */
|
||||
|
@ -120,8 +130,8 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
val sender = event.senderId ?: return
|
||||
val flowId = getFlowId(event) ?: return
|
||||
|
||||
val verification = this.getExistingTransaction(sender, flowId) ?: return
|
||||
val request = this.olmMachine.getVerificationRequest(sender, flowId)
|
||||
val verification = getExistingTransaction(sender, flowId) ?: return
|
||||
val request = olmMachine.getVerificationRequest(sender, flowId)
|
||||
|
||||
if (request != null && request.isReady()) {
|
||||
// If this is a SAS verification originating from a `m.key.verification.request`
|
||||
|
@ -132,59 +142,54 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
Timber.d("## Verification: Auto accepting SAS verification with $sender")
|
||||
verification.accept()
|
||||
} else {
|
||||
this.dispatcher.dispatchTxUpdated(verification)
|
||||
dispatcher.dispatchTxUpdated(verification)
|
||||
}
|
||||
} else {
|
||||
// This didn't originate from a request, so tell our listeners that
|
||||
// this is a new verification.
|
||||
this.dispatcher.dispatchTxAdded(verification)
|
||||
dispatcher.dispatchTxAdded(verification)
|
||||
// The IncomingVerificationRequestHandler seems to only listen to updates
|
||||
// so let's trigger an update after the addition as well.
|
||||
this.dispatcher.dispatchTxUpdated(verification)
|
||||
dispatcher.dispatchTxUpdated(verification)
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if the request event created a nev verification request object and dispatch that it dis so */
|
||||
private fun onRequest(event: Event) {
|
||||
val flowId = getFlowId(event) ?: return
|
||||
private fun onRequest(event: Event, fromRoomMessage: Boolean) {
|
||||
val flowId = if (fromRoomMessage) {
|
||||
event.eventId
|
||||
} else {
|
||||
event.getClearContent().toModel<ToDeviceVerificationEvent>()?.transactionId
|
||||
} ?: return
|
||||
val sender = event.senderId ?: return
|
||||
val request = getExistingVerificationRequest(sender, flowId) ?: return
|
||||
|
||||
val request = this.getExistingVerificationRequest(sender, flowId) ?: return
|
||||
|
||||
this.dispatcher.dispatchRequestAdded(request)
|
||||
dispatcher.dispatchRequestAdded(request)
|
||||
}
|
||||
|
||||
override fun addListener(listener: VerificationService.Listener) {
|
||||
this.dispatcher.addListener(listener)
|
||||
dispatcher.addListener(listener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: VerificationService.Listener) {
|
||||
this.dispatcher.removeListener(listener)
|
||||
dispatcher.removeListener(listener)
|
||||
}
|
||||
|
||||
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||
// TODO this doesn't seem to be used anymore?
|
||||
runBlocking {
|
||||
val device = olmMachine.getDevice(userId, deviceID)
|
||||
device?.markAsTrusted()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) {
|
||||
// TODO This should be handled inside the rust-sdk decryption method
|
||||
override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||
olmMachine.getDevice(userId, deviceID)?.markAsTrusted()
|
||||
}
|
||||
|
||||
override fun getExistingTransaction(
|
||||
otherUserId: String,
|
||||
tid: String,
|
||||
): VerificationTransaction? {
|
||||
return this.olmMachine.getVerification(otherUserId, tid)
|
||||
return olmMachine.getVerification(otherUserId, tid)
|
||||
}
|
||||
|
||||
override fun getExistingVerificationRequests(
|
||||
otherUserId: String
|
||||
): List<PendingVerificationRequest> {
|
||||
return this.olmMachine.getVerificationRequests(otherUserId).map {
|
||||
return olmMachine.getVerificationRequests(otherUserId).map {
|
||||
it.toPendingVerificationRequest()
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +199,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
tid: String?
|
||||
): PendingVerificationRequest? {
|
||||
return if (tid != null) {
|
||||
this.olmMachine.getVerificationRequest(otherUserId, tid)?.toPendingVerificationRequest()
|
||||
olmMachine.getVerificationRequest(otherUserId, tid)?.toPendingVerificationRequest()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -224,29 +229,24 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
return null
|
||||
}
|
||||
|
||||
override fun requestKeyVerification(
|
||||
methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
otherDevices: List<String>?
|
||||
): PendingVerificationRequest {
|
||||
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
||||
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
|
||||
override suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest {
|
||||
val verification = when (val identity = olmMachine.getIdentity(olmMachine.userId())) {
|
||||
is OwnUserIdentity -> identity.requestVerification(methods)
|
||||
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
|
||||
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
|
||||
}
|
||||
|
||||
return verification.toPendingVerificationRequest()
|
||||
}
|
||||
|
||||
override fun requestKeyVerificationInDMs(
|
||||
override suspend fun requestKeyVerificationInDMs(
|
||||
methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
localId: String?
|
||||
): PendingVerificationRequest {
|
||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
||||
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
|
||||
olmMachine.ensureUsersKeys(listOf(otherUserId))
|
||||
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
|
||||
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
|
||||
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
|
||||
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
|
||||
}
|
||||
|
@ -254,21 +254,20 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
return verification.toPendingVerificationRequest()
|
||||
}
|
||||
|
||||
override fun readyPendingVerification(
|
||||
override suspend fun readyPendingVerification(
|
||||
methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
transactionId: String
|
||||
): Boolean {
|
||||
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||
|
||||
val request = olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||
return if (request != null) {
|
||||
runBlocking { request.acceptWithMethods(methods) }
|
||||
request.acceptWithMethods(methods)
|
||||
|
||||
if (request.isReady()) {
|
||||
val qrcode = request.startQrVerification()
|
||||
|
||||
if (qrcode != null) {
|
||||
this.dispatcher.dispatchTxAdded(qrcode)
|
||||
dispatcher.dispatchTxAdded(qrcode)
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -280,82 +279,50 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||
}
|
||||
}
|
||||
|
||||
override fun readyPendingVerificationInDMs(
|
||||
methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
transactionId: String
|
||||
): Boolean {
|
||||
return readyPendingVerification(methods, otherUserId, transactionId)
|
||||
}
|
||||
|
||||
override fun beginKeyVerification(
|
||||
override suspend fun beginKeyVerification(
|
||||
method: VerificationMethod,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
transactionId: String?
|
||||
transactionId: String
|
||||
): String? {
|
||||
return if (method == VerificationMethod.SAS) {
|
||||
if (transactionId != null) {
|
||||
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||
val request = olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||
|
||||
runBlocking {
|
||||
val sas = request?.startSasVerification()
|
||||
val sas = request?.startSasVerification()
|
||||
|
||||
if (sas != null) {
|
||||
dispatcher.dispatchTxAdded(sas)
|
||||
sas.transactionId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (sas != null) {
|
||||
dispatcher.dispatchTxAdded(sas)
|
||||
sas.transactionId
|
||||
} else {
|
||||
// This starts the short SAS flow, the one that doesn't start with
|
||||
// a `m.key.verification.request`, Element web stopped doing this, might
|
||||
// be wise do do so as well
|
||||
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
|
||||
// triggers this is called `manuallyVerify()`
|
||||
runBlocking {
|
||||
val verification = olmMachine.getDevice(otherUserId, otherDeviceId)?.startVerification()
|
||||
if (verification != null) {
|
||||
dispatcher.dispatchTxAdded(verification)
|
||||
verification.transactionId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown verification method")
|
||||
}
|
||||
}
|
||||
|
||||
override fun beginKeyVerificationInDMs(
|
||||
method: VerificationMethod,
|
||||
transactionId: String,
|
||||
roomId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String
|
||||
): String {
|
||||
beginKeyVerification(method, otherUserId, otherDeviceId, transactionId)
|
||||
// TODO what's the point of returning the same ID we got as an argument?
|
||||
// We do this because the old verification service did so
|
||||
return transactionId
|
||||
}
|
||||
|
||||
override fun cancelVerificationRequest(request: PendingVerificationRequest) {
|
||||
val verificationRequest = request.transactionId?.let {
|
||||
this.olmMachine.getVerificationRequest(request.otherUserId, it)
|
||||
override suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String? {
|
||||
// This starts the short SAS flow, the one that doesn't start with
|
||||
// a `m.key.verification.request`, Element web stopped doing this, might
|
||||
// be wise do do so as well
|
||||
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
|
||||
// triggers this is called `manuallyVerify()`
|
||||
val otherDevice = olmMachine.getDevice(otherUserId, otherDeviceId)
|
||||
val verification = otherDevice?.startVerification()
|
||||
return if (verification != null) {
|
||||
dispatcher.dispatchTxAdded(verification)
|
||||
verification.transactionId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
runBlocking { verificationRequest?.cancel() }
|
||||
}
|
||||
|
||||
override fun declineVerificationRequestInDMs(
|
||||
otherUserId: String,
|
||||
transactionId: String,
|
||||
roomId: String
|
||||
) {
|
||||
val verificationRequest = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||
runBlocking { verificationRequest?.cancel() }
|
||||
override suspend fun cancelVerificationRequest(request: PendingVerificationRequest) {
|
||||
request.transactionId ?: return
|
||||
cancelVerificationRequest(request.otherUserId, request.transactionId)
|
||||
}
|
||||
|
||||
override suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String) {
|
||||
val verificationRequest = olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||
verificationRequest?.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -177,7 +177,6 @@ internal class DefaultSession @Inject constructor(
|
|||
assert(!isOpen)
|
||||
isOpen = true
|
||||
globalErrorHandler.listener = this
|
||||
cryptoService.get().ensureDevice()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach {
|
||||
it.onSessionStarted(this)
|
||||
|
|
|
@ -49,7 +49,6 @@ import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
|||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||
import org.matrix.android.sdk.internal.session.search.SearchTask
|
||||
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import java.security.InvalidParameterException
|
||||
|
||||
internal class DefaultRoom(override val roomId: String,
|
||||
|
@ -117,9 +116,7 @@ internal class DefaultRoom(override val roomId: String,
|
|||
}
|
||||
|
||||
override suspend fun prepareToEncrypt() {
|
||||
awaitCallback<Unit> {
|
||||
cryptoService.prepareToEncrypt(roomId, it)
|
||||
}
|
||||
cryptoService.prepareToEncrypt(roomId)
|
||||
}
|
||||
|
||||
override suspend fun enableEncryption(algorithm: String, force: Boolean) {
|
||||
|
|
|
@ -156,7 +156,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
|
|||
* Invoke the event decryption mechanism for a specific event
|
||||
*/
|
||||
|
||||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
||||
private suspend fun decryptIfNeeded(event: Event, roomId: String) {
|
||||
try {
|
||||
// Event from sync does not have roomId, so add it to the event first
|
||||
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
||||
|
|
|
@ -43,6 +43,7 @@ internal class RealmSendingEventsDataSource(
|
|||
private var roomEntity: RoomEntity? = null
|
||||
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
||||
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
||||
private val builtEvents = ArrayList<TimelineEvent>()
|
||||
|
||||
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
|
||||
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
||||
|
|
|
@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
|
|||
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -126,7 +127,9 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||
return
|
||||
}
|
||||
try {
|
||||
val result = cryptoService.decryptEvent(request.event, timelineId)
|
||||
val result = runBlocking {
|
||||
cryptoService.decryptEvent(request.event, timelineId)
|
||||
}
|
||||
Timber.v("Successfully decrypted event ${event.eventId}")
|
||||
realm.executeTransaction {
|
||||
val eventId = event.eventId ?: return@executeTransaction
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
|
|||
import dagger.Lazy
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
@ -343,7 +344,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
return roomEntity
|
||||
}
|
||||
|
||||
private suspend fun handleTimelineEvents(realm: Realm,
|
||||
private fun handleTimelineEvents(realm: Realm,
|
||||
roomId: String,
|
||||
roomEntity: RoomEntity,
|
||||
eventList: List<Event>,
|
||||
|
@ -458,7 +459,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
||||
try {
|
||||
// Event from sync does not have roomId, so add it to the event first
|
||||
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
||||
val result = runBlocking {
|
||||
cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
||||
}
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
|
|
|
@ -134,10 +134,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
|
|||
onView(withId(R.id.bottomSheetFragmentContainer))
|
||||
.check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
|
||||
|
||||
val request = existingSession!!.cryptoService().verificationService().requestKeyVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
existingSession!!.myUserId,
|
||||
listOf(uiSession.sessionParams.deviceId!!)
|
||||
val request = existingSession!!.cryptoService().verificationService().requestSelfKeyVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
|
||||
)
|
||||
|
||||
val transactionId = request.transactionId!!
|
||||
|
|
|
@ -64,12 +64,12 @@ fun Session.startSyncing(context: Context) {
|
|||
/**
|
||||
* Tell is the session has unsaved e2e keys in the backup
|
||||
*/
|
||||
fun Session.hasUnsavedKeys(): Boolean {
|
||||
suspend fun Session.hasUnsavedKeys(): Boolean {
|
||||
return cryptoService().inboundGroupSessionsCount(false) > 0 &&
|
||||
cryptoService().keysBackupService().state != KeysBackupState.ReadyToBackUp
|
||||
}
|
||||
|
||||
fun Session.cannotLogoutSafely(): Boolean {
|
||||
suspend fun Session.cannotLogoutSafely(): Boolean {
|
||||
// has some encrypted chat
|
||||
return hasUnsavedKeys() ||
|
||||
// has local cross signing keys
|
||||
|
|
|
@ -26,5 +26,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
|
|||
data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async<KeysBackupVersionTrust> = Uninitialized,
|
||||
val keysBackupState: KeysBackupState? = null,
|
||||
val keysBackupVersion: KeysVersionResult? = null,
|
||||
val remainingKeysToBackup: Int = 0,
|
||||
val deleteBackupRequest: Async<Unit> = Uninitialized) :
|
||||
MavericksState
|
||||
|
|
|
@ -124,10 +124,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
|
|||
style(ItemStyle.BIG_TEXT)
|
||||
hasIndeterminateProcess(true)
|
||||
|
||||
val totalKeys = host.session.cryptoService().inboundGroupSessionsCount(false)
|
||||
val backedUpKeys = host.session.cryptoService().inboundGroupSessionsCount(true)
|
||||
|
||||
val remainingKeysToBackup = totalKeys - backedUpKeys
|
||||
val remainingKeysToBackup = data.remainingKeysToBackup
|
||||
|
||||
if (data.keysBackupVersionTrust()?.usable == false) {
|
||||
description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence())
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.app.features.crypto.keysbackup.settings
|
|||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
@ -32,7 +31,6 @@ import org.matrix.android.sdk.api.session.Session
|
|||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import timber.log.Timber
|
||||
|
||||
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
||||
session: Session
|
||||
|
@ -46,6 +44,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||
|
||||
companion object : MavericksViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private val cryptoService = session.cryptoService()
|
||||
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
|
||||
|
||||
init {
|
||||
|
@ -75,34 +74,12 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||
|
||||
private fun getKeysBackupTrust() = withState { state ->
|
||||
val versionResult = keysBackupService.keysBackupVersion
|
||||
Timber.d("BACKUP: HEEEEEEE $versionResult ${state.keysBackupVersionTrust}")
|
||||
|
||||
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Loading(),
|
||||
deleteBackupRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
Timber.d("BACKUP: HEEEEEEE TWO")
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val data = keysBackupService.getKeysBackupTrust(versionResult)
|
||||
Timber.d("BACKUP: HEEEE suceeeded $data")
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Success(data)
|
||||
)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.d("BACKUP: HEEEE FAILED $failure")
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
setState { copy(deleteBackupRequest = Uninitialized) }
|
||||
suspend {
|
||||
keysBackupService.getKeysBackupTrust(versionResult)
|
||||
}.execute {
|
||||
copy(keysBackupVersionTrust = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,10 +96,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||
keysBackupVersion = keysBackupService.keysBackupVersion
|
||||
)
|
||||
}
|
||||
|
||||
when (newState) {
|
||||
KeysBackupState.BackingUp, KeysBackupState.WillBackUp -> updateKeysCount()
|
||||
else -> Unit
|
||||
}
|
||||
getKeysBackupTrust()
|
||||
}
|
||||
|
||||
private fun updateKeysCount() {
|
||||
viewModelScope.launch {
|
||||
val totalKeys = cryptoService.inboundGroupSessionsCount(false)
|
||||
val backedUpKeys = cryptoService.inboundGroupSessionsCount(true)
|
||||
val remainingKeysToBackup = totalKeys - backedUpKeys
|
||||
setState {
|
||||
copy(remainingKeysToBackup = remainingKeysToBackup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteCurrentBackup() {
|
||||
val keysBackupService = keysBackupService
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ class KeyRequestHandler @Inject constructor(
|
|||
}
|
||||
|
||||
if (deviceInfo.isUnknown) {
|
||||
session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false), userId, deviceId)
|
||||
session?.cryptoService()?.verificationService()?.markedLocallyAsManuallyVerified(userId, deviceId)
|
||||
|
||||
deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||
|
||||
|
|
|
@ -149,10 +149,12 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||
// as we are going to reset, we'd better cancel all outgoing requests
|
||||
// if not they could be accepted in the middle of the reset process
|
||||
// and cause strange use cases
|
||||
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
|
||||
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
||||
viewModelScope.launch {
|
||||
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
|
||||
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
||||
}
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
|
||||
}
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
|
||||
}
|
||||
|
||||
private fun handleResetAll() {
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
|
|||
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
@ -91,12 +90,7 @@ class BootstrapCrossSigningTask @Inject constructor(
|
|||
)
|
||||
|
||||
try {
|
||||
awaitCallback<Unit> {
|
||||
crossSigningService.initializeCrossSigning(
|
||||
params.userInteractiveAuthInterceptor,
|
||||
it
|
||||
)
|
||||
}
|
||||
crossSigningService.initializeCrossSigning(params.userInteractiveAuthInterceptor)
|
||||
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
|
||||
return BootstrapResult.SuccessCrossSigningOnly
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
|
|||
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import im.vector.app.features.popup.VerificationVectorAlert
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
|
@ -42,7 +44,8 @@ import javax.inject.Singleton
|
|||
class IncomingVerificationRequestHandler @Inject constructor(
|
||||
private val context: Context,
|
||||
private var avatarRenderer: Provider<AvatarRenderer>,
|
||||
private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
|
||||
private val popupAlertManager: PopupAlertManager,
|
||||
private val coroutineScope: CoroutineScope) : VerificationService.Listener {
|
||||
|
||||
private var session: Session? = null
|
||||
|
||||
|
@ -61,7 +64,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||
// TODO maybe check also if
|
||||
val uid = "kvr_${tx.transactionId}"
|
||||
when (tx.state) {
|
||||
is VerificationTxState.OnStarted -> {
|
||||
is VerificationTxState.OnStarted -> {
|
||||
// Add a notification for every incoming request
|
||||
val user = session?.getUser(tx.otherUserId)
|
||||
val name = user?.toMatrixItem()?.getBestName() ?: tx.otherUserId
|
||||
|
@ -88,12 +91,14 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
|
||||
}
|
||||
}
|
||||
dismissedAction = Runnable {
|
||||
dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
|
||||
tx.cancel()
|
||||
}
|
||||
addButton(
|
||||
context.getString(R.string.action_ignore),
|
||||
{ tx.cancel() }
|
||||
LaunchCoroutineRunnable(coroutineScope) {
|
||||
tx.cancel()
|
||||
}
|
||||
)
|
||||
addButton(
|
||||
context.getString(R.string.action_open),
|
||||
|
@ -160,10 +165,9 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
dismissedAction = Runnable {
|
||||
session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
|
||||
pr.transactionId ?: "",
|
||||
pr.roomId ?: ""
|
||||
dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
|
||||
session?.cryptoService()?.verificationService()?.cancelVerificationRequest(pr.otherUserId,
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
}
|
||||
colorAttribute = R.attr.vctr_notice_secondary
|
||||
|
@ -181,6 +185,14 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private class LaunchCoroutineRunnable(private val coroutineScope: CoroutineScope, private val block: suspend () -> Unit) : Runnable {
|
||||
override fun run() {
|
||||
coroutineScope.launch {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
|
||||
"verificationRequest_${pr.transactionId}"
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
package im.vector.app.features.crypto.verification
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
|
@ -131,34 +129,37 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction
|
||||
}
|
||||
|
||||
val hasAnyOtherSession = session.cryptoService()
|
||||
.getCryptoDeviceInfo(session.myUserId)
|
||||
.any {
|
||||
it.deviceId != session.sessionParams.deviceId
|
||||
}
|
||||
viewModelScope.launch {
|
||||
|
||||
setState {
|
||||
copy(
|
||||
otherUserMxItem = userItem?.toMatrixItem(),
|
||||
sasTransactionState = sasTx?.state,
|
||||
qrTransactionState = qrTx?.state,
|
||||
transactionId = pr?.transactionId ?: initialState.verificationId,
|
||||
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
|
||||
isMe = initialState.otherUserId == session.myUserId,
|
||||
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
|
||||
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
|
||||
hasAnyOtherSession = hasAnyOtherSession
|
||||
)
|
||||
}
|
||||
val hasAnyOtherSession = session.cryptoService()
|
||||
.getCryptoDeviceInfoList(session.myUserId)
|
||||
.any {
|
||||
it.deviceId != session.sessionParams.deviceId
|
||||
}
|
||||
|
||||
if (autoReady) {
|
||||
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
||||
session.cryptoService().verificationService()
|
||||
.readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
pr!!.otherUserId,
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
setState {
|
||||
copy(
|
||||
otherUserMxItem = userItem?.toMatrixItem(),
|
||||
sasTransactionState = sasTx?.state,
|
||||
qrTransactionState = qrTx?.state,
|
||||
transactionId = pr?.transactionId ?: initialState.verificationId,
|
||||
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
|
||||
isMe = initialState.otherUserId == session.myUserId,
|
||||
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
|
||||
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
|
||||
hasAnyOtherSession = hasAnyOtherSession
|
||||
)
|
||||
}
|
||||
|
||||
if (autoReady) {
|
||||
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
||||
session.cryptoService().verificationService()
|
||||
.readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
pr!!.otherUserId,
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,14 +193,16 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
|
||||
session.cryptoService()
|
||||
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
|
||||
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
||||
}
|
||||
session.cryptoService()
|
||||
.verificationService()
|
||||
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
|
||||
?.cancel(CancelCode.User)
|
||||
viewModelScope.launch {
|
||||
session.cryptoService()
|
||||
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
|
||||
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
||||
}
|
||||
session.cryptoService()
|
||||
.verificationService()
|
||||
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
|
||||
?.cancel(CancelCode.User)
|
||||
}
|
||||
}
|
||||
|
||||
fun continueFromCancel() {
|
||||
|
@ -232,109 +235,25 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
|
||||
when (action) {
|
||||
is VerificationAction.RequestVerificationByDM -> {
|
||||
if (roomId == null) {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
setState {
|
||||
copy(
|
||||
pendingLocalId = localId,
|
||||
pendingRequest = Loading()
|
||||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = runCatching { session.createDirectRoom(otherUserId) }
|
||||
result.fold(
|
||||
{ data ->
|
||||
setState {
|
||||
copy(
|
||||
roomId = data,
|
||||
pendingRequest = Success(
|
||||
session
|
||||
.cryptoService()
|
||||
.verificationService()
|
||||
.requestKeyVerificationInDMs(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
otherUserId,
|
||||
data,
|
||||
pendingLocalId
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ failure ->
|
||||
setState {
|
||||
copy(pendingRequest = Fail(failure))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
pendingRequest = Success(session
|
||||
.cryptoService()
|
||||
.verificationService()
|
||||
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Unit
|
||||
handleRequestVerificationByDM(roomId, otherUserId)
|
||||
}
|
||||
is VerificationAction.StartSASVerification -> {
|
||||
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
|
||||
?: return@withState
|
||||
val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
|
||||
if (roomId == null) {
|
||||
session.cryptoService().verificationService().beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
otherUserId = request.otherUserId,
|
||||
otherDeviceId = otherDevice ?: "",
|
||||
transactionId = action.pendingRequestTransactionId
|
||||
)
|
||||
} else {
|
||||
session.cryptoService().verificationService().beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
transactionId = action.pendingRequestTransactionId,
|
||||
roomId = roomId,
|
||||
otherUserId = request.otherUserId,
|
||||
otherDeviceId = otherDevice ?: ""
|
||||
)
|
||||
}
|
||||
Unit
|
||||
handleStartSASVerification(otherUserId, action)
|
||||
}
|
||||
is VerificationAction.RemoteQrCodeScanned -> {
|
||||
val existingTransaction = session.cryptoService().verificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.userHasScannedOtherQrCode(action.scannedData)
|
||||
handleRemoteQrCodeScanned(action)
|
||||
}
|
||||
is VerificationAction.OtherUserScannedSuccessfully -> {
|
||||
val transactionId = state.transactionId ?: return@withState
|
||||
|
||||
val existingTransaction = session.cryptoService().verificationService()
|
||||
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.otherUserScannedMyQrCode()
|
||||
handleOtherUserScannedSuccessfully(state.transactionId, otherUserId)
|
||||
}
|
||||
is VerificationAction.OtherUserDidNotScanned -> {
|
||||
val transactionId = state.transactionId ?: return@withState
|
||||
|
||||
val existingTransaction = session.cryptoService().verificationService()
|
||||
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.otherUserDidNotScannedMyQrCode()
|
||||
handleOtherUserDidNotScanned(state.transactionId, otherUserId)
|
||||
}
|
||||
is VerificationAction.SASMatchAction -> {
|
||||
(session.cryptoService().verificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
|
||||
handleSASMatchAction(action)
|
||||
}
|
||||
is VerificationAction.SASDoNotMatchAction -> {
|
||||
(session.cryptoService().verificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||
as? SasVerificationTransaction)
|
||||
?.shortCodeDoesNotMatch()
|
||||
handleSASDoNotMatchAction(action)
|
||||
}
|
||||
is VerificationAction.GotItConclusion -> {
|
||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||
|
@ -365,6 +284,85 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleStartSASVerification(otherUserId: String, action: VerificationAction.StartSASVerification) {
|
||||
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
|
||||
?: return
|
||||
viewModelScope.launch {
|
||||
session.cryptoService().verificationService().beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
otherUserId = request.otherUserId,
|
||||
transactionId = action.pendingRequestTransactionId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSASDoNotMatchAction(action: VerificationAction.SASDoNotMatchAction) {
|
||||
viewModelScope.launch {
|
||||
(session.cryptoService().verificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||
as? SasVerificationTransaction)
|
||||
?.shortCodeDoesNotMatch()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSASMatchAction(action: VerificationAction.SASMatchAction) {
|
||||
viewModelScope.launch {
|
||||
(session.cryptoService().verificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOtherUserDidNotScanned(transactionId: String?, otherUserId: String) {
|
||||
transactionId ?: return
|
||||
viewModelScope.launch {
|
||||
val existingTransaction = session.cryptoService().verificationService()
|
||||
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.otherUserDidNotScannedMyQrCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOtherUserScannedSuccessfully(transactionId: String?, otherUserId: String) {
|
||||
transactionId ?: return
|
||||
viewModelScope.launch {
|
||||
val existingTransaction = session.cryptoService().verificationService()
|
||||
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.otherUserScannedMyQrCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRemoteQrCodeScanned(action: VerificationAction.RemoteQrCodeScanned) {
|
||||
viewModelScope.launch {
|
||||
val existingTransaction = session.cryptoService().verificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.userHasScannedOtherQrCode(action.scannedData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRequestVerificationByDM(roomId: String?, otherUserId: String) {
|
||||
viewModelScope.launch {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
val dmRoomId = roomId ?: session.createDirectRoom(otherUserId)
|
||||
setState { copy(pendingLocalId = localId, roomId = dmRoomId) }
|
||||
suspend {
|
||||
session
|
||||
.cryptoService()
|
||||
.verificationService()
|
||||
.requestKeyVerificationInDMs(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
otherUserId,
|
||||
dmRoomId,
|
||||
localId
|
||||
)
|
||||
}.execute {
|
||||
copy(pendingRequest = it, roomId = dmRoomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
|
@ -446,60 +444,66 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleTransactionUpdate(state: VerificationBottomSheetViewState, tx: VerificationTransaction) {
|
||||
viewModelScope.launch {
|
||||
if (state.selfVerificationMode && state.transactionId == null) {
|
||||
// is this an incoming with that user
|
||||
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
|
||||
// Also auto accept incoming if needed!
|
||||
// TODO is state.transactionId ever null for self verifications, doesn't seem
|
||||
// like this will ever trigger
|
||||
if (tx is SasVerificationTransaction && tx.state == VerificationTxState.OnStarted) {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
/*
|
||||
if (tx is IncomingSasVerificationTransaction) {
|
||||
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
tx.performAccept()
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Use this one!
|
||||
setState {
|
||||
copy(
|
||||
transactionId = tx.transactionId,
|
||||
sasTransactionState = tx.state.takeIf { tx is SasVerificationTransaction },
|
||||
qrTransactionState = tx.state.takeIf { tx is QrCodeVerificationTransaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (tx) {
|
||||
is SasVerificationTransaction -> {
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A SAS tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
sasTransactionState = tx.state
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is QrCodeVerificationTransaction -> {
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A QR tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
qrTransactionState = tx.state
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun transactionCreated(tx: VerificationTransaction) {
|
||||
transactionUpdated(tx)
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
|
||||
if (state.selfVerificationMode && state.transactionId == null) {
|
||||
// is this an incoming with that user
|
||||
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
|
||||
// Also auto accept incoming if needed!
|
||||
// TODO is state.transactionId ever null for self verifications, doesn't seem
|
||||
// like this will ever trigger
|
||||
if (tx is SasVerificationTransaction && tx.state == VerificationTxState.OnStarted) {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
/*
|
||||
if (tx is IncomingSasVerificationTransaction) {
|
||||
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
tx.performAccept()
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Use this one!
|
||||
setState {
|
||||
copy(
|
||||
transactionId = tx.transactionId,
|
||||
sasTransactionState = tx.state.takeIf { tx is SasVerificationTransaction },
|
||||
qrTransactionState = tx.state.takeIf { tx is QrCodeVerificationTransaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (tx) {
|
||||
is SasVerificationTransaction -> {
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A SAS tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
sasTransactionState = tx.state
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is QrCodeVerificationTransaction -> {
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A QR tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
qrTransactionState = tx.state
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
handleTransactionUpdate(state, tx)
|
||||
}
|
||||
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
|
@ -514,12 +518,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||
if (!pr.isReady) {
|
||||
// auto ready in this case, as we are waiting
|
||||
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
||||
session.cryptoService().verificationService()
|
||||
.readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
pr.otherUserId,
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
viewModelScope.launch {
|
||||
session.cryptoService().verificationService()
|
||||
.readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
pr.otherUserId,
|
||||
pr.transactionId ?: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Use this one!
|
||||
|
|
|
@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
|
|||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
@ -220,30 +219,27 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
// Try to initialize cross signing in background if possible
|
||||
Timber.d("Initialize cross signing...")
|
||||
try {
|
||||
awaitCallback<Unit> {
|
||||
session.cryptoService().crossSigningService().initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
// We missed server grace period or it's not setup, see if we remember locally password
|
||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
||||
errCode == null &&
|
||||
reAuthHelper.data != null) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
session = flowResponse.session,
|
||||
user = session.myUserId,
|
||||
password = reAuthHelper.data
|
||||
)
|
||||
)
|
||||
} else {
|
||||
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
|
||||
}
|
||||
session.cryptoService().crossSigningService().initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
// We missed server grace period or it's not setup, see if we remember locally password
|
||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
||||
errCode == null &&
|
||||
reAuthHelper.data != null) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
session = flowResponse.session,
|
||||
user = session.myUserId,
|
||||
password = reAuthHelper.data
|
||||
)
|
||||
)
|
||||
} else {
|
||||
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
|
||||
}
|
||||
},
|
||||
callback = it
|
||||
)
|
||||
Timber.d("Initialize cross signing SUCCESS")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
Timber.d("Initialize cross signing SUCCESS")
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Failed to initialize cross signing")
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ class HomeDrawerFragment @Inject constructor(
|
|||
}
|
||||
// Sign out
|
||||
views.homeDrawerHeaderSignoutView.debouncedClicks {
|
||||
signout()
|
||||
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
|
||||
SignOutUiWorker(requireActivity()).perform()
|
||||
}
|
||||
|
@ -118,4 +119,7 @@ class HomeDrawerFragment @Inject constructor(
|
|||
navigator.openDebug(requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
private fun signout() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,10 +32,11 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
@ -58,7 +59,7 @@ data class DeviceDetectionInfo(
|
|||
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
|
||||
session: Session,
|
||||
private val vectorPreferences: VectorPreferences) :
|
||||
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
|
||||
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
|
||||
|
||||
sealed class Action : VectorViewModelAction {
|
||||
data class IgnoreDevice(val deviceIds: List<String>) : Action()
|
||||
|
@ -75,12 +76,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
init {
|
||||
|
||||
val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
|
||||
.firstOrNull { it.deviceId == session.sessionParams.deviceId }
|
||||
?.firstTimeSeenLocalTs
|
||||
?: System.currentTimeMillis()
|
||||
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
|
||||
|
||||
ignoredDeviceList.addAll(
|
||||
vectorPreferences.getUnknownDeviceDismissedList().also {
|
||||
Timber.v("## Detector - Remembered ignored list $it")
|
||||
|
@ -90,10 +85,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||
combine(
|
||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||
session.flow().liveMyDevicesInfo(),
|
||||
session.flow().liveCrossSigningPrivateKeys()
|
||||
) { cryptoList, infoList, pInfo ->
|
||||
session.flow().liveCrossSigningPrivateKeys(),
|
||||
session.firstTimeDeviceSeen(),
|
||||
) { cryptoList, infoList, pInfo, firstTimeDeviceSeen ->
|
||||
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
|
||||
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
|
||||
Timber.v("## Detector - Current Session first time seen $firstTimeDeviceSeen")
|
||||
infoList
|
||||
.filter { info ->
|
||||
// filter verified session, by checking the crypto device info
|
||||
|
@ -106,7 +103,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
|
||||
DeviceDetectionInfo(
|
||||
deviceInfo,
|
||||
deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive,
|
||||
deviceKnownSince > firstTimeDeviceSeen + 60_000, // short window to avoid false positive,
|
||||
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
|
||||
)
|
||||
}
|
||||
|
@ -125,12 +122,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||
.sample(5_000)
|
||||
.onEach {
|
||||
// If we have a new crypto device change, we might want to trigger refresh of device info
|
||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||
session.cryptoService().fetchDevicesList()
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
// trigger a refresh of lastSeen / last Ip
|
||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||
viewModelScope.launch {
|
||||
session.cryptoService().fetchDevicesList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: Action) {
|
||||
|
@ -154,4 +153,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
private fun Session.firstTimeDeviceSeen() = flow {
|
||||
val value = cryptoService().getCryptoDeviceInfoList(myUserId)
|
||||
.firstOrNull { it.deviceId == sessionParams.deviceId }
|
||||
?.firstTimeSeenLocalTs
|
||||
?: System.currentTimeMillis()
|
||||
emit(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -994,22 +994,24 @@ class TimelineViewModel @AssistedInject constructor(
|
|||
|
||||
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
||||
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
|
||||
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
action.otherUserId,
|
||||
room.roomId,
|
||||
action.transactionId)) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||
} else {
|
||||
// TODO
|
||||
viewModelScope.launch {
|
||||
if (session.cryptoService().verificationService().readyPendingVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
action.otherUserId,
|
||||
action.transactionId)) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
||||
session.cryptoService().verificationService().declineVerificationRequestInDMs(
|
||||
action.otherUserId,
|
||||
action.transactionId,
|
||||
room.roomId)
|
||||
viewModelScope.launch {
|
||||
session.cryptoService().verificationService().cancelVerificationRequest(
|
||||
action.otherUserId,
|
||||
action.transactionId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
|
||||
|
|
|
@ -164,11 +164,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
|
||||
val nonNullTimelineEvent = timelineEvent() ?: return@onEach
|
||||
eventIdFlow.tryEmit(nonNullTimelineEvent.eventId)
|
||||
val events = actionsForEvent(nonNullTimelineEvent, permissions)
|
||||
setState {
|
||||
copy(
|
||||
eventId = nonNullTimelineEvent.eventId,
|
||||
messageBody = computeMessageBody(nonNullTimelineEvent),
|
||||
actions = actionsForEvent(nonNullTimelineEvent, permissions)
|
||||
actions = events
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +247,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
}
|
||||
}
|
||||
|
||||
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
||||
private suspend fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
||||
val messageContent = timelineEvent.getLastMessageContent()
|
||||
val msgType = messageContent?.msgType
|
||||
|
||||
|
@ -317,7 +318,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
// TODO sent by me or sufficient power level
|
||||
}
|
||||
|
||||
private fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
|
||||
private suspend fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
|
||||
actionPermissions: ActionPermissions,
|
||||
messageContent: MessageContent?,
|
||||
msgType: String?) {
|
||||
|
@ -400,7 +401,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
) {
|
||||
add(EventSharedAction.UseKeyBackup)
|
||||
}
|
||||
if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1 ||
|
||||
if (session.cryptoService().getCryptoDeviceInfoList(session.myUserId).size > 1 ||
|
||||
timelineEvent.senderInfo.userId != session.myUserId) {
|
||||
add(EventSharedAction.ReRequestKey(timelineEvent.eventId))
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
|||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.matrix.android.sdk.api.crypto.VerificationState
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -138,53 +139,55 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
}
|
||||
}
|
||||
|
||||
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration {
|
||||
return if (
|
||||
event.root.sendState == SendState.SYNCED &&
|
||||
roomSummary?.isEncrypted.orFalse() &&
|
||||
// is user verified
|
||||
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true) {
|
||||
val ts = roomSummary?.encryptionEventTs ?: 0
|
||||
val eventTs = event.root.originServerTs ?: 0
|
||||
if (event.isEncrypted()) {
|
||||
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
|
||||
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
|
||||
E2EDecoration.NONE
|
||||
} else {
|
||||
val sendingDevice = event.root.content
|
||||
.toModel<EncryptedEventContent>()
|
||||
?.deviceId
|
||||
?.let { deviceId ->
|
||||
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
|
||||
}
|
||||
when {
|
||||
sendingDevice == null -> {
|
||||
// For now do not decorate this with warning
|
||||
// maybe it's a deleted session
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
sendingDevice.trustLevel == null -> {
|
||||
E2EDecoration.WARN_SENT_BY_UNKNOWN
|
||||
}
|
||||
sendingDevice.trustLevel?.isVerified().orFalse() -> {
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
else -> {
|
||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration = runBlocking {
|
||||
if (event.root.sendState != SendState.SYNCED) {
|
||||
return@runBlocking E2EDecoration.NONE
|
||||
}
|
||||
if (!roomSummary?.isEncrypted.orFalse()) {
|
||||
return@runBlocking E2EDecoration.NONE
|
||||
}
|
||||
val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
|
||||
if (!isUserVerified) {
|
||||
return@runBlocking E2EDecoration.NONE
|
||||
}
|
||||
val ts = roomSummary?.encryptionEventTs ?: 0
|
||||
val eventTs = event.root.originServerTs ?: 0
|
||||
return@runBlocking if (event.isEncrypted()) {
|
||||
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
|
||||
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
|
||||
E2EDecoration.NONE
|
||||
} else {
|
||||
if (event.root.isStateEvent()) {
|
||||
// Do not warn for state event, they are always in clear
|
||||
E2EDecoration.NONE
|
||||
} else {
|
||||
// If event is in clear after the room enabled encryption we should warn
|
||||
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
|
||||
val sendingDevice = event.root.content
|
||||
.toModel<EncryptedEventContent>()
|
||||
?.deviceId
|
||||
?.let { deviceId ->
|
||||
session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId)
|
||||
}
|
||||
when {
|
||||
sendingDevice == null -> {
|
||||
// For now do not decorate this with warning
|
||||
// maybe it's a deleted session
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
sendingDevice.trustLevel == null -> {
|
||||
E2EDecoration.WARN_SENT_BY_UNKNOWN
|
||||
}
|
||||
sendingDevice.trustLevel?.isVerified().orFalse() -> {
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
else -> {
|
||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
E2EDecoration.NONE
|
||||
if (event.root.isStateEvent()) {
|
||||
// Do not warn for state event, they are always in clear
|
||||
E2EDecoration.NONE
|
||||
} else {
|
||||
// If event is in clear after the room enabled encryption we should warn
|
||||
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,8 @@ import im.vector.app.features.terms.ReviewTermsActivity
|
|||
import im.vector.app.features.widgets.WidgetActivity
|
||||
import im.vector.app.features.widgets.WidgetArgsBuilder
|
||||
import im.vector.app.space
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
|
@ -116,7 +118,8 @@ class DefaultNavigator @Inject constructor(
|
|||
private val widgetArgsBuilder: WidgetArgsBuilder,
|
||||
private val appStateHandler: AppStateHandler,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||
private val features: VectorFeatures
|
||||
private val features: VectorFeatures,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : Navigator {
|
||||
|
||||
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
|
||||
|
@ -193,55 +196,57 @@ class DefaultNavigator @Inject constructor(
|
|||
}
|
||||
|
||||
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
||||
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
|
||||
?: return
|
||||
if (tx is SasVerificationTransaction && tx.isIncoming) {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
coroutineScope.launch {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
|
||||
?: return@launch
|
||||
if (tx is SasVerificationTransaction && tx.isIncoming) {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
|
||||
if (context is AppCompatActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = otherUserId,
|
||||
transactionId = sasTransactionId
|
||||
).show(context.supportFragmentManager, "REQPOP")
|
||||
if (context is AppCompatActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = otherUserId,
|
||||
transactionId = sasTransactionId
|
||||
).show(context.supportFragmentManager, "REQPOP")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestSessionVerification(context: Context, otherSessionId: String) {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
||||
val pr = session.cryptoService().verificationService().requestKeyVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
session.myUserId,
|
||||
listOf(otherSessionId)
|
||||
)
|
||||
if (context is AppCompatActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = session.myUserId,
|
||||
transactionId = pr.transactionId
|
||||
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||
coroutineScope.launch {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||
val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
|
||||
supportedVerificationMethodsProvider.provide()
|
||||
)
|
||||
if (context is AppCompatActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = session.myUserId,
|
||||
transactionId = pr.transactionId
|
||||
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestSelfSessionVerification(context: Context) {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
||||
val otherSessions = session.cryptoService()
|
||||
.getCryptoDeviceInfo(session.myUserId)
|
||||
.filter { it.deviceId != session.sessionParams.deviceId }
|
||||
.map { it.deviceId }
|
||||
if (context is AppCompatActivity) {
|
||||
if (otherSessions.isNotEmpty()) {
|
||||
val pr = session.cryptoService().verificationService().requestKeyVerification(
|
||||
supportedVerificationMethodsProvider.provide(),
|
||||
session.myUserId,
|
||||
otherSessions)
|
||||
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
|
||||
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||
} else {
|
||||
VerificationBottomSheet.forSelfVerification(session)
|
||||
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||
coroutineScope.launch {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||
val otherSessions = session.cryptoService()
|
||||
.getCryptoDeviceInfoList(session.myUserId)
|
||||
.filter { it.deviceId != session.sessionParams.deviceId }
|
||||
.map { it.deviceId }
|
||||
if (context is AppCompatActivity) {
|
||||
if (otherSessions.isNotEmpty()) {
|
||||
val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
|
||||
supportedVerificationMethodsProvider.provide())
|
||||
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
|
||||
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||
} else {
|
||||
VerificationBottomSheet.forSelfVerification(session)
|
||||
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -404,13 +409,15 @@ class DefaultNavigator @Inject constructor(
|
|||
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {
|
||||
// if cross signing is enabled and trusted or not set up at all we should propose full 4S
|
||||
sessionHolder.getSafeActiveSession()?.let { session ->
|
||||
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
|
||||
session.cryptoService().crossSigningService().canCrossSign()) {
|
||||
(context as? AppCompatActivity)?.let {
|
||||
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
|
||||
coroutineScope.launch {
|
||||
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
|
||||
session.cryptoService().crossSigningService().canCrossSign()) {
|
||||
(context as? AppCompatActivity)?.let {
|
||||
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
|
||||
}
|
||||
} else {
|
||||
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
|
||||
}
|
||||
} else {
|
||||
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ class NotifiableEventResolver @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
|
||||
private suspend fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
|
||||
if (root.isEncrypted() && root.mxDecryptionResult == null) {
|
||||
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||
// for now decrypt sync
|
||||
|
|
|
@ -30,9 +30,9 @@ import im.vector.app.core.di.SingletonEntryPoint
|
|||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
|
@ -124,8 +124,13 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
|
|||
|
||||
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
|
||||
if (!initialState.allowDeviceAction) return
|
||||
session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, initialState.userId, action.deviceId, null)?.let { txID ->
|
||||
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
|
||||
viewModelScope.launch {
|
||||
session.cryptoService().verificationService().beginDeviceVerification(
|
||||
otherUserId = initialState.userId,
|
||||
otherDeviceId = action.deviceId,
|
||||
)?.let { txID ->
|
||||
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.roomprofile.members
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
@ -94,8 +93,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
|||
if (room.isEncrypted()) {
|
||||
room.flow().liveRoomMembers(roomMemberQueryParams)
|
||||
.flatMapLatest { membersSummary ->
|
||||
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
|
||||
.asFlow()
|
||||
session.cryptoService().getLiveCryptoDeviceInfoList(membersSummary.map { it.userId })
|
||||
.catch { Timber.e(it) }
|
||||
.map { deviceList ->
|
||||
// If any key change, emit the userIds list
|
||||
|
|
|
@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import me.gujun.android.span.span
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||
|
@ -317,31 +315,32 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
|
||||
// Todo this should be refactored and use same state as 4S section
|
||||
private fun refreshXSigningStatus() {
|
||||
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||
lifecycleScope.launchWhenResumed {
|
||||
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||
|
||||
when {
|
||||
xSigningKeyCanSign -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
||||
}
|
||||
xSigningKeysAreTrusted -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
||||
}
|
||||
xSigningIsEnableInAccount -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
|
||||
}
|
||||
else -> {
|
||||
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
|
||||
when {
|
||||
xSigningKeyCanSign -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
||||
}
|
||||
xSigningKeysAreTrusted -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
||||
}
|
||||
xSigningIsEnableInAccount -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
|
||||
}
|
||||
else -> {
|
||||
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
|
||||
}
|
||||
}
|
||||
mCrossSigningStatePreference.isVisible = true
|
||||
}
|
||||
|
||||
mCrossSigningStatePreference.isVisible = true
|
||||
}
|
||||
|
||||
private val saveMegolmStartForActivityResult = registerStartForActivityResult {
|
||||
|
@ -523,7 +522,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
/**
|
||||
* Build the cryptography preference section.
|
||||
*/
|
||||
private fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
|
||||
private suspend fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
|
||||
showDeviceListPref.isEnabled = devices.isNotEmpty()
|
||||
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
|
||||
|
||||
|
@ -553,7 +552,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
}
|
||||
|
||||
// crypto section: device key (fingerprint)
|
||||
val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId)
|
||||
val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId)
|
||||
|
||||
val fingerprint = deviceInfo?.fingerprint()
|
||||
if (fingerprint?.isNotEmpty() == true) {
|
||||
|
@ -579,28 +578,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
// ==============================================================================================================
|
||||
|
||||
private fun refreshMyDevice() {
|
||||
session.cryptoService().getUserDevices(session.myUserId).map {
|
||||
DeviceInfo(
|
||||
userId = session.myUserId,
|
||||
deviceId = it.deviceId,
|
||||
displayName = it.displayName()
|
||||
)
|
||||
}.let {
|
||||
refreshCryptographyPreference(it)
|
||||
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
||||
session.cryptoService().getUserDevices(session.myUserId).map {
|
||||
DeviceInfo(
|
||||
userId = session.myUserId,
|
||||
deviceId = it.deviceId,
|
||||
displayName = it.displayName()
|
||||
)
|
||||
}.let {
|
||||
refreshCryptographyPreference(it)
|
||||
}
|
||||
// TODO Move to a ViewModel...
|
||||
val devicesList = session.cryptoService().fetchDevicesList()
|
||||
refreshCryptographyPreference(devicesList)
|
||||
}
|
||||
// TODO Move to a ViewModel...
|
||||
session.cryptoService().fetchDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
if (isAdded) {
|
||||
refreshCryptographyPreference(data.devices.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (isAdded) {
|
||||
refreshCryptographyPreference(emptyList())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.app.features.auth.ReAuthActivity
|
|||
import im.vector.app.features.login.ReAuthHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
|
@ -41,7 +42,6 @@ import org.matrix.android.sdk.flow.flow
|
|||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
@ -55,25 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
|
||||
|
||||
init {
|
||||
combine(
|
||||
session.flow().liveMyDevicesInfo(),
|
||||
session.flow().liveCrossSigningInfo(session.myUserId)
|
||||
) { myDevicesInfo, mxCrossSigningInfo ->
|
||||
myDevicesInfo to mxCrossSigningInfo
|
||||
}
|
||||
.execute { data ->
|
||||
val crossSigningKeys = data.invoke()?.second?.getOrNull()
|
||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||
|
||||
copy(
|
||||
crossSigningInfo = crossSigningKeys,
|
||||
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
|
||||
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
|
||||
xSigningKeyCanSign = xSigningKeyCanSign
|
||||
)
|
||||
}
|
||||
observeCrossSigning()
|
||||
}
|
||||
|
||||
var uiaContinuation: Continuation<UIABaseAuth>? = null
|
||||
|
@ -90,29 +72,27 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
awaitCallback<Unit> {
|
||||
session.cryptoService().crossSigningService().initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse,
|
||||
errCode: String?,
|
||||
promise: Continuation<UIABaseAuth>) {
|
||||
Timber.d("## UIA : initializeCrossSigning UIA")
|
||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
||||
reAuthHelper.data != null && errCode == null) {
|
||||
UserPasswordAuth(
|
||||
session = null,
|
||||
user = session.myUserId,
|
||||
password = reAuthHelper.data
|
||||
).let { promise.resume(it) }
|
||||
} else {
|
||||
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
|
||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||
uiaContinuation = promise
|
||||
}
|
||||
session.cryptoService().crossSigningService().initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse,
|
||||
errCode: String?,
|
||||
promise: Continuation<UIABaseAuth>) {
|
||||
Timber.d("## UIA : initializeCrossSigning UIA")
|
||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
||||
reAuthHelper.data != null && errCode == null) {
|
||||
UserPasswordAuth(
|
||||
session = null,
|
||||
user = session.myUserId,
|
||||
password = reAuthHelper.data
|
||||
).let { promise.resume(it) }
|
||||
} else {
|
||||
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
|
||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||
uiaContinuation = promise
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (failure: Throwable) {
|
||||
handleInitializeXSigningError(failure)
|
||||
} finally {
|
||||
|
@ -149,6 +129,28 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun observeCrossSigning() {
|
||||
combine(
|
||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||
session.flow().liveCrossSigningInfo(session.myUserId)
|
||||
) { myDevicesInfo, mxCrossSigningInfo ->
|
||||
myDevicesInfo to mxCrossSigningInfo
|
||||
}.onEach { data ->
|
||||
val crossSigningKeys = data.second.getOrNull()
|
||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||
setState {
|
||||
copy(
|
||||
crossSigningInfo = crossSigningKeys,
|
||||
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
|
||||
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
|
||||
xSigningKeyCanSign = xSigningKeyCanSign
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInitializeXSigningError(failure: Throwable) {
|
||||
Timber.e(failure, "## CrossSigning - Failed to initialize cross signing")
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing))))
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package im.vector.app.features.settings.devices
|
||||
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
@ -26,6 +25,7 @@ import im.vector.app.core.platform.EmptyAction
|
|||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
|
@ -43,14 +43,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||
by hiltMavericksViewModelFactory()
|
||||
|
||||
init {
|
||||
|
||||
setState {
|
||||
copy(
|
||||
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
|
||||
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
|
||||
isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
|
||||
)
|
||||
}
|
||||
initState()
|
||||
session.flow().liveCrossSigningInfo(session.myUserId)
|
||||
.execute {
|
||||
copy(
|
||||
|
@ -78,10 +71,6 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||
)
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(deviceInfo = Loading())
|
||||
}
|
||||
|
||||
session.flow().liveMyDevicesInfo()
|
||||
.map { devices ->
|
||||
devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId)
|
||||
|
@ -91,6 +80,21 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||
}
|
||||
}
|
||||
|
||||
private fun initState() {
|
||||
viewModelScope.launch {
|
||||
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
||||
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||
val isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
|
||||
setState {
|
||||
copy(
|
||||
hasAccountCrossSigning = hasAccountCrossSigning,
|
||||
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
|
||||
isRecoverySetup = isRecoverySetup
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import im.vector.app.core.utils.PublishDataSource
|
|||
import im.vector.app.features.auth.ReAuthActivity
|
||||
import im.vector.app.features.login.ReAuthHelper
|
||||
import im.vector.lib.core.utils.flow.throttleFirst
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -43,8 +42,6 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
|
@ -53,17 +50,14 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
|||
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import timber.log.Timber
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
|
@ -106,15 +100,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
private val refreshSource = PublishDataSource<Unit>()
|
||||
|
||||
init {
|
||||
|
||||
setState {
|
||||
copy(
|
||||
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
|
||||
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
|
||||
myDeviceId = session.sessionParams.deviceId ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
initState()
|
||||
combine(
|
||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||
session.flow().liveMyDevicesInfo()
|
||||
|
@ -155,7 +141,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
.sample(5_000)
|
||||
.onEach {
|
||||
// If we have a new crypto device change, we might want to trigger refresh of device info
|
||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||
session.cryptoService().fetchDevicesList()
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
|
@ -168,7 +154,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
|
||||
refreshSource.stream().throttleFirst(4_000)
|
||||
.onEach {
|
||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||
session.cryptoService().fetchDevicesList()
|
||||
session.cryptoService().downloadKeys(listOf(session.myUserId), true)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
@ -176,6 +162,21 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
queryRefreshDevicesList()
|
||||
}
|
||||
|
||||
private fun initState() {
|
||||
viewModelScope.launch {
|
||||
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
||||
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||
val myDeviceId = session.sessionParams.deviceId ?: ""
|
||||
setState {
|
||||
copy(
|
||||
hasAccountCrossSigning = hasAccountCrossSigning,
|
||||
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
|
||||
myDeviceId = myDeviceId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
session.cryptoService().verificationService().removeListener(this)
|
||||
super.onCleared()
|
||||
|
@ -240,13 +241,15 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
|
||||
val txID = session.cryptoService()
|
||||
.verificationService()
|
||||
.beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null)
|
||||
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
|
||||
session.myUserId,
|
||||
txID
|
||||
))
|
||||
viewModelScope.launch {
|
||||
val txID = session.cryptoService()
|
||||
.verificationService()
|
||||
.beginDeviceVerification(session.myUserId, action.deviceId)
|
||||
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
|
||||
session.myUserId,
|
||||
txID
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
|
||||
|
@ -268,8 +271,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
}
|
||||
} else {
|
||||
// legacy
|
||||
session.cryptoService().setDeviceVerification(
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
session.cryptoService().verificationService().markedLocallyAsManuallyVerified(
|
||||
action.cryptoDeviceInfo.userId,
|
||||
action.cryptoDeviceInfo.deviceId)
|
||||
}
|
||||
|
@ -288,27 +290,21 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleRename(action: DevicesAction.Rename) {
|
||||
session.cryptoService().setDeviceName(action.deviceId, action.newName, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
session.cryptoService().setDeviceName(action.deviceId, action.newName)
|
||||
setState {
|
||||
copy(
|
||||
request = Success(data)
|
||||
)
|
||||
copy(request = Success(Unit))
|
||||
}
|
||||
// force settings update
|
||||
queryRefreshDevicesList()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
request = Fail(failure)
|
||||
)
|
||||
copy(request = Fail(failure))
|
||||
}
|
||||
|
||||
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -323,39 +319,32 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
awaitCallback<Unit> {
|
||||
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
Timber.d("## UIA : deleteDevice UIA")
|
||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
|
||||
UserPasswordAuth(
|
||||
session = null,
|
||||
user = session.myUserId,
|
||||
password = reAuthHelper.data
|
||||
).let { promise.resume(it) }
|
||||
} else {
|
||||
Timber.d("## UIA : deleteDevice UIA > start reauth activity")
|
||||
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
|
||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||
uiaContinuation = promise
|
||||
}
|
||||
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
Timber.d("## UIA : deleteDevice UIA")
|
||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
|
||||
UserPasswordAuth(
|
||||
session = null,
|
||||
user = session.myUserId,
|
||||
password = reAuthHelper.data
|
||||
).let { promise.resume(it) }
|
||||
} else {
|
||||
Timber.d("## UIA : deleteDevice UIA > start reauth activity")
|
||||
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
|
||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||
uiaContinuation = promise
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
request = Success(Unit)
|
||||
)
|
||||
copy(request = Success(Unit))
|
||||
}
|
||||
// force settings update
|
||||
queryRefreshDevicesList()
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
request = Fail(failure)
|
||||
)
|
||||
copy(request = Fail(failure))
|
||||
}
|
||||
if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
|
||||
_viewEvents.post(DevicesViewEvents.Failure(Exception(stringProvider.getString(R.string.authentication_error))))
|
||||
|
|
|
@ -95,7 +95,7 @@ class SoftLogoutActivity : LoginActivity() {
|
|||
MainActivity.restartApp(this, MainActivityArgs())
|
||||
}
|
||||
|
||||
views.loginLoading.isVisible = softLogoutViewState.isLoading()
|
||||
views.loginLoading.isVisible = softLogoutViewState.isLoading
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.app.features.signout.soft.epoxy.loginRedButtonItem
|
|||
import im.vector.app.features.signout.soft.epoxy.loginTextItem
|
||||
import im.vector.app.features.signout.soft.epoxy.loginTitleItem
|
||||
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import javax.inject.Inject
|
||||
|
||||
class SoftLogoutController @Inject constructor(
|
||||
|
@ -52,6 +53,7 @@ class SoftLogoutController @Inject constructor(
|
|||
|
||||
override fun buildModels() {
|
||||
val safeViewState = viewState ?: return
|
||||
if (safeViewState.hasUnsavedKeys is Incomplete) return
|
||||
|
||||
buildHeader(safeViewState)
|
||||
buildForm(safeViewState)
|
||||
|
@ -78,7 +80,7 @@ class SoftLogoutController @Inject constructor(
|
|||
state.userDisplayName,
|
||||
state.userId))
|
||||
}
|
||||
if (state.hasUnsavedKeys) {
|
||||
if (state.hasUnsavedKeys().orFalse()) {
|
||||
loginTextItem {
|
||||
id("signText2")
|
||||
text(host.stringProvider.getString(R.string.soft_logout_signin_e2e_warning_notice))
|
||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.features.login.AbstractLoginFragment
|
|||
import im.vector.app.features.login.LoginAction
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.LoginViewEvents
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -118,7 +119,7 @@ class SoftLogoutFragment @Inject constructor(
|
|||
withState(softLogoutViewModel) { state ->
|
||||
cleanupUi()
|
||||
|
||||
val messageResId = if (state.hasUnsavedKeys) {
|
||||
val messageResId = if (state.hasUnsavedKeys().orFalse()) {
|
||||
R.string.soft_logout_clear_data_dialog_e2e_warning_content
|
||||
} else {
|
||||
R.string.soft_logout_clear_data_dialog_content
|
||||
|
|
|
@ -69,7 +69,6 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
userId = userId,
|
||||
deviceId = session.sessionParams.deviceId.orEmpty(),
|
||||
userDisplayName = session.getUser(userId)?.displayName ?: userId,
|
||||
hasUnsavedKeys = session.hasUnsavedKeys()
|
||||
)
|
||||
} else {
|
||||
SoftLogoutViewState(
|
||||
|
@ -77,17 +76,25 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
userId = "",
|
||||
deviceId = "",
|
||||
userDisplayName = "",
|
||||
hasUnsavedKeys = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
checkHasUnsavedKeys()
|
||||
// Get the supported login flow
|
||||
getSupportedLoginFlow()
|
||||
}
|
||||
|
||||
private fun checkHasUnsavedKeys() {
|
||||
suspend {
|
||||
session.hasUnsavedKeys()
|
||||
}.execute {
|
||||
copy(hasUnsavedKeys = it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSupportedLoginFlow() {
|
||||
viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
|
|
@ -30,13 +30,12 @@ data class SoftLogoutViewState(
|
|||
val userId: String,
|
||||
val deviceId: String,
|
||||
val userDisplayName: String,
|
||||
val hasUnsavedKeys: Boolean,
|
||||
val hasUnsavedKeys: Async<Boolean> = Uninitialized,
|
||||
val enteredPassword: String = ""
|
||||
) : MavericksState {
|
||||
|
||||
fun isLoading(): Boolean {
|
||||
return asyncLoginAction is Loading ||
|
||||
val isLoading: Boolean =
|
||||
asyncLoginAction is Loading ||
|
||||
// Keep loading when it is success because of the delay to switch to the next Activity
|
||||
asyncLoginAction is Success
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,14 +130,14 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
|
|||
/**
|
||||
* Safe way to get the number of keys to backup
|
||||
*/
|
||||
fun getNumberOfKeysToBackup(): Int {
|
||||
private suspend fun getNumberOfKeysToBackup(): Int {
|
||||
return session.cryptoService().inboundGroupSessionsCount(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe way to tell if there are more keys on the server
|
||||
*/
|
||||
fun canRestoreKeys(): Boolean {
|
||||
private suspend fun canRestoreKeys(): Boolean {
|
||||
return session.cryptoService().keysBackupService().canRestoreKeys()
|
||||
}
|
||||
|
||||
|
@ -161,5 +161,5 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
|
|||
}
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {}
|
||||
override fun handle(action: EmptyAction) = Unit
|
||||
}
|
||||
|
|
|
@ -17,17 +17,25 @@
|
|||
package im.vector.app.features.workers.signout
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cannotLogoutSafely
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
class SignOutUiWorker(private val activity: FragmentActivity) {
|
||||
|
||||
fun perform() {
|
||||
val session = activity.singletonEntryPoint().activeSessionHolder().getSafeActiveSession() ?: return
|
||||
activity.lifecycleScope.perform(session)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.perform(session: Session) = launch {
|
||||
if (session.cannotLogoutSafely()) {
|
||||
// The backup check on logout flow has to be displayed if there are keys in the store, and the keys backup state is not Ready
|
||||
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance()
|
||||
|
|
|
@ -30,9 +30,9 @@ class FakeCryptoService : CryptoService by mockk() {
|
|||
|
||||
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
|
||||
override fun getLiveCryptoDeviceInfoList(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
|
||||
override fun getLiveCryptoDeviceInfoList(userIds: List<String>) = MutableLiveData(
|
||||
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue