mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-29 15:40:55 +08: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
@ -125,21 +125,21 @@ class FlowSession(private val session: Session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> {
|
fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> {
|
||||||
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow()
|
return session.cryptoService().getLiveCryptoDeviceInfoList(userId)
|
||||||
.startWith(session.coroutineDispatchers.io) {
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
session.cryptoService().getCryptoDeviceInfo(userId)
|
session.cryptoService().getCryptoDeviceInfoList(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
||||||
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow()
|
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId)
|
||||||
.startWith(session.coroutineDispatchers.io) {
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
|
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
|
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
|
||||||
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow()
|
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys()
|
||||||
.startWith(session.coroutineDispatchers.io) {
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
|
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,9 @@ dependencies {
|
|||||||
implementation libs.jetbrains.coroutinesAndroid
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
|
|
||||||
implementation 'org.matrix.rustcomponents:crypto-android:0.1.1-SNAPSHOT'
|
implementation 'org.matrix.rustcomponents:crypto-android:0.1.1-SNAPSHOT'
|
||||||
|
//implementation files('libs/crypto-android-release.aar')
|
||||||
|
|
||||||
|
// implementation(name: 'crypto-android-release', ext: 'aar')
|
||||||
implementation 'net.java.dev.jna:jna:5.10.0@aar'
|
implementation 'net.java.dev.jna:jna:5.10.0@aar'
|
||||||
|
|
||||||
implementation libs.androidx.appCompat
|
implementation libs.androidx.appCompat
|
||||||
|
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))
|
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: suspend (() -> Boolean)) {
|
||||||
while (true) {
|
while (true) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
if (condition()) {
|
if (condition()) {
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.common
|
|||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
@ -29,8 +30,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
|||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
@ -275,7 +275,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun initializeCrossSigning(session: Session) {
|
fun initializeCrossSigning(session: Session) {
|
||||||
testHelper.doSync<Unit> {
|
testHelper.runBlockingTest {
|
||||||
session.cryptoService().crossSigningService()
|
session.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -288,7 +288,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,24 +300,24 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||||||
val aliceVerificationService = alice.cryptoService().verificationService()
|
val aliceVerificationService = alice.cryptoService().verificationService()
|
||||||
val bobVerificationService = bob.cryptoService().verificationService()
|
val bobVerificationService = bob.cryptoService().verificationService()
|
||||||
|
|
||||||
aliceVerificationService.beginKeyVerificationInDMs(
|
runBlocking {
|
||||||
VerificationMethod.SAS,
|
aliceVerificationService.beginKeyVerification(
|
||||||
requestID,
|
VerificationMethod.SAS,
|
||||||
roomId,
|
roomId,
|
||||||
bob.myUserId,
|
bob.myUserId,)
|
||||||
bob.sessionParams.credentials.deviceId!!)
|
}
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
// we should reach SHOW SAS on both
|
||||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
var alicePovTx: SasVerificationTransaction? = null
|
||||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
var bobPovTx: SasVerificationTransaction? = null
|
||||||
|
|
||||||
// wait for alice to get the ready
|
// wait for alice to get the ready
|
||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
testHelper.retryPeriodicallyWithLatch(it) {
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction
|
||||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
|
||||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||||
bobPovTx?.performAccept()
|
bobPovTx?.acceptVerification()
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -327,18 +327,18 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||||||
|
|
||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
testHelper.retryPeriodicallyWithLatch(it) {
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
|
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? SasVerificationTransaction
|
||||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
Log.v("TEST", "== alicePovTx is ${alicePovTx?.state}")
|
||||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// wait for alice to get the ready
|
// wait for alice to get the ready
|
||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
testHelper.retryPeriodicallyWithLatch(it) {
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction
|
||||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
|
||||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||||
bobPovTx?.performAccept()
|
bobPovTx?.acceptVerification()
|
||||||
}
|
}
|
||||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||||
}
|
}
|
||||||
@ -346,8 +346,10 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||||||
|
|
||||||
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
||||||
|
|
||||||
bobPovTx!!.userHasVerifiedShortCode()
|
runBlocking {
|
||||||
alicePovTx!!.userHasVerifiedShortCode()
|
bobPovTx!!.userHasVerifiedShortCode()
|
||||||
|
alicePovTx!!.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
|
||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
testHelper.retryPeriodicallyWithLatch(it) {
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
@ -60,8 +60,8 @@ class PreShareKeysTest : InstrumentedTest {
|
|||||||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||||
|
|
||||||
// Force presharing of new outbound key
|
// Force presharing of new outbound key
|
||||||
testHelper.doSync<Unit> {
|
testHelper.runBlockingTest {
|
||||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
|
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.waitWithLatch { latch ->
|
testHelper.waitWithLatch { latch ->
|
||||||
|
@ -144,9 +144,12 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
// - Store the olm session between A&B devices
|
// - Store the olm session between A&B devices
|
||||||
// Let us pickle our session with bob here so we can later unpickle it
|
// Let us pickle our session with bob here so we can later unpickle it
|
||||||
// and wedge our session.
|
// and wedge our session.
|
||||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
var myDevice = testHelper.runBlockingTest {
|
||||||
|
bobSession.cryptoService().getMyCryptoDevice()
|
||||||
|
}
|
||||||
|
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(myDevice.identityKey()!!)
|
||||||
sessionIdsForBob!!.size shouldBe 1
|
sessionIdsForBob!!.size shouldBe 1
|
||||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), myDevice.identityKey()!!)!!
|
||||||
|
|
||||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||||
|
|
||||||
@ -174,7 +177,10 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
// Let us wedge the session now. Set crypto state like after the first message
|
// Let us wedge the session now. Set crypto state like after the first message
|
||||||
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
|
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
|
||||||
|
|
||||||
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
|
myDevice = testHelper.runBlockingTest {
|
||||||
|
bobSession.cryptoService().getMyCryptoDevice()
|
||||||
|
}
|
||||||
|
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), myDevice.identityKey()!!)
|
||||||
Thread.sleep(6_000)
|
Thread.sleep(6_000)
|
||||||
|
|
||||||
// Force new session, and key share
|
// Force new session, and key share
|
||||||
@ -207,7 +213,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||||
|
|
||||||
// It's a trick to force key request on fail to decrypt
|
// It's a trick to force key request on fail to decrypt
|
||||||
testHelper.doSync<Unit> {
|
testHelper.runBlockingTest {
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -220,7 +226,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until we received back the key
|
// Wait until we received back the key
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
@ -53,7 +54,7 @@ class XSigningTest : InstrumentedTest {
|
|||||||
fun test_InitializeAndStoreKeys() {
|
fun test_InitializeAndStoreKeys() {
|
||||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
testHelper.doSync<Unit> {
|
testHelper.runBlockingTest {
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
@ -65,10 +66,12 @@ class XSigningTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
val myCrossSigningKeys = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||||
|
}
|
||||||
val masterPubKey = myCrossSigningKeys?.masterKey()
|
val masterPubKey = myCrossSigningKeys?.masterKey()
|
||||||
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
||||||
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
||||||
@ -78,7 +81,10 @@ class XSigningTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
||||||
|
|
||||||
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
val userTrustResult = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId)
|
||||||
|
}
|
||||||
|
assertTrue("Signing Keys should be trusted", userTrustResult.isVerified())
|
||||||
|
|
||||||
testHelper.signOutAndClose(aliceSession)
|
testHelper.signOutAndClose(aliceSession)
|
||||||
}
|
}
|
||||||
@ -99,29 +105,37 @@ class XSigningTest : InstrumentedTest {
|
|||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.doSync<Unit> {
|
testHelper.runBlockingTest {
|
||||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
promise.resume(aliceAuthParams)
|
promise.resume(aliceAuthParams)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
|
}
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(bobAuthParams)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
|
||||||
promise.resume(bobAuthParams)
|
|
||||||
}
|
|
||||||
}, it) }
|
|
||||||
|
|
||||||
// Check that alice can see bob keys
|
// Check that alice can see bob keys
|
||||||
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true) }
|
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true) }
|
||||||
|
|
||||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
val bobKeysFromAlicePOV = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||||
|
}
|
||||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||||
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||||
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
||||||
|
|
||||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
|
val myKeys = testHelper.runBlockingTest {
|
||||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
|
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
|
||||||
|
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||||
|
|
||||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||||
|
|
||||||
@ -145,25 +159,33 @@ class XSigningTest : InstrumentedTest {
|
|||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
testHelper.runBlockingTest {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
promise.resume(aliceAuthParams)
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
}
|
promise.resume(aliceAuthParams)
|
||||||
}, it) }
|
}
|
||||||
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
})
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
}
|
||||||
promise.resume(bobAuthParams)
|
testHelper.runBlockingTest {
|
||||||
}
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
}, it) }
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(bobAuthParams)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Check that alice can see bob keys
|
// Check that alice can see bob keys
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) }
|
testHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) }
|
||||||
|
|
||||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
val bobKeysFromAlicePOV = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||||
|
}
|
||||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||||
|
|
||||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().crossSigningService().trustUser(bobUserId)
|
||||||
|
}
|
||||||
|
|
||||||
// Now bobs logs in on a new device and verifies it
|
// Now bobs logs in on a new device and verifies it
|
||||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||||
@ -180,7 +202,9 @@ class XSigningTest : InstrumentedTest {
|
|||||||
fail("Bob should see the new device")
|
fail("Bob should see the new device")
|
||||||
}
|
}
|
||||||
|
|
||||||
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
|
val bobSecondDevicePOVFirstDevice = runBlocking {
|
||||||
|
bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||||
|
}
|
||||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||||
|
|
||||||
// Manually mark it as trusted from first session
|
// Manually mark it as trusted from first session
|
||||||
@ -198,7 +222,9 @@ class XSigningTest : InstrumentedTest {
|
|||||||
fail("Alice should see the new device")
|
fail("Alice should see the new device")
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
val result = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||||
|
}
|
||||||
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||||
|
|
||||||
testHelper.signOutAndClose(aliceSession)
|
testHelper.signOutAndClose(aliceSession)
|
||||||
|
@ -23,6 +23,7 @@ import junit.framework.TestCase.assertEquals
|
|||||||
import junit.framework.TestCase.assertNotNull
|
import junit.framework.TestCase.assertNotNull
|
||||||
import junit.framework.TestCase.assertTrue
|
import junit.framework.TestCase.assertTrue
|
||||||
import junit.framework.TestCase.fail
|
import junit.framework.TestCase.fail
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
|||||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
@ -50,7 +50,6 @@ import org.matrix.android.sdk.common.SessionTestParams
|
|||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
|
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
|
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@ -93,7 +92,9 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
assert(receivedEvent!!.isEncrypted())
|
assert(receivedEvent!!.isEncrypted())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
commonTestHelper.runBlockingTest {
|
||||||
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
|
}
|
||||||
fail("should fail")
|
fail("should fail")
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
}
|
}
|
||||||
@ -106,7 +107,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
|
|
||||||
var outGoingRequestId: String? = null
|
var outGoingRequestId: String? = null
|
||||||
|
|
||||||
commonTestHelper.waitWithLatch { latch ->
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
.filter { req ->
|
.filter { req ->
|
||||||
@ -148,14 +149,17 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
commonTestHelper.runBlockingTest {
|
||||||
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
|
}
|
||||||
fail("should fail")
|
fail("should fail")
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the device as trusted
|
// Mark the device as trusted
|
||||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
commonTestHelper.runBlockingTest {
|
||||||
aliceSession2.sessionParams.deviceId ?: "")
|
aliceSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession2.sessionParams.deviceId ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
// Re request
|
// Re request
|
||||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||||
@ -185,7 +189,9 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
commonTestHelper.runBlockingTest {
|
||||||
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
fail("should have been able to decrypt")
|
fail("should have been able to decrypt")
|
||||||
}
|
}
|
||||||
@ -199,7 +205,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
fun test_ShareSSSSSecret() {
|
fun test_ShareSSSSSecret() {
|
||||||
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
commonTestHelper.doSync<Unit> {
|
commonTestHelper.runBlockingTest {
|
||||||
aliceSession1.cryptoService().crossSigningService()
|
aliceSession1.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -211,7 +217,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also bootstrap keybackup on first session
|
// Also bootstrap keybackup on first session
|
||||||
@ -242,27 +248,30 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
|
|
||||||
aliceVerificationService1.addListener(object : VerificationService.Listener {
|
aliceVerificationService1.addListener(object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx !is SasVerificationTransaction) return
|
||||||
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||||
if (tx is SasVerificationTransaction) {
|
when (tx.state) {
|
||||||
if (tx.state == VerificationTxState.OnStarted) {
|
VerificationTxState.OnStarted -> commonTestHelper.runBlockingTest {
|
||||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
tx.acceptVerification()
|
||||||
}
|
}
|
||||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
VerificationTxState.ShortCodeReady -> commonTestHelper.runBlockingTest {
|
||||||
session1ShortCode = tx.getDecimalCodeRepresentation()
|
session1ShortCode = tx.getDecimalCodeRepresentation()
|
||||||
Thread.sleep(500)
|
delay(500)
|
||||||
tx.userHasVerifiedShortCode()
|
tx.userHasVerifiedShortCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
aliceVerificationService2.addListener(object : VerificationService.Listener {
|
aliceVerificationService2.addListener(object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx !is SasVerificationTransaction) return
|
||||||
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||||
if (tx is SasVerificationTransaction) {
|
when (tx.state) {
|
||||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
VerificationTxState.ShortCodeReady -> commonTestHelper.runBlockingTest {
|
||||||
session2ShortCode = tx.getDecimalCodeRepresentation()
|
session2ShortCode = tx.getDecimalCodeRepresentation()
|
||||||
Thread.sleep(500)
|
delay(500)
|
||||||
tx.userHasVerifiedShortCode()
|
tx.userHasVerifiedShortCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,12 +279,13 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
})
|
})
|
||||||
|
|
||||||
val txId = "m.testVerif12"
|
val txId = "m.testVerif12"
|
||||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
commonTestHelper.runBlockingTest {
|
||||||
?: "", txId)
|
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, txId)
|
||||||
|
}
|
||||||
|
|
||||||
commonTestHelper.waitWithLatch { latch ->
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
aliceSession1.cryptoService().getCryptoDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +322,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
fun test_ImproperKeyShareBug() {
|
fun test_ImproperKeyShareBug() {
|
||||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
commonTestHelper.doSync<Unit> {
|
commonTestHelper.runBlockingTest {
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -325,7 +335,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an encrypted room and send a couple of messages
|
// Create an encrypted room and send a couple of messages
|
||||||
@ -346,7 +356,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
// Create bob session
|
// Create bob session
|
||||||
|
|
||||||
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
||||||
commonTestHelper.doSync<Unit> {
|
commonTestHelper.runBlockingTest {
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -359,7 +369,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let alice invite bob
|
// Let alice invite bob
|
||||||
@ -380,7 +390,10 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
val roomRoomBobPov = aliceSession.getRoom(roomId)
|
val roomRoomBobPov = aliceSession.getRoom(roomId)
|
||||||
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
|
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
|
||||||
|
|
||||||
var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
|
var dRes =
|
||||||
|
commonTestHelper.runBlockingTest {
|
||||||
|
tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
|
||||||
|
}
|
||||||
|
|
||||||
assert(dRes == null)
|
assert(dRes == null)
|
||||||
|
|
||||||
@ -391,7 +404,9 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
Thread.sleep(3_000)
|
Thread.sleep(3_000)
|
||||||
|
|
||||||
// With the bug the first session would have improperly reshare that key :/
|
// With the bug the first session would have improperly reshare that key :/
|
||||||
dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
|
dRes = commonTestHelper.runBlockingTest {
|
||||||
|
tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
|
||||||
|
}
|
||||||
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
|
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
|
||||||
assert(dRes?.clearEvent == null)
|
assert(dRes?.clearEvent == null)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.gossiping
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -92,7 +93,9 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
// Bob should not be able to decrypt because the keys is withheld
|
// Bob should not be able to decrypt because the keys is withheld
|
||||||
try {
|
try {
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
runBlocking {
|
||||||
|
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||||
|
}
|
||||||
Assert.fail("This session should not be able to decrypt")
|
Assert.fail("This session should not be able to decrypt")
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
@ -117,7 +120,9 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
// Previous message should still be undecryptable (partially withheld session)
|
// Previous message should still be undecryptable (partially withheld session)
|
||||||
try {
|
try {
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
runBlocking {
|
||||||
|
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||||
|
}
|
||||||
Assert.fail("This session should not be able to decrypt")
|
Assert.fail("This session should not be able to decrypt")
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
@ -164,7 +169,9 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
||||||
try {
|
try {
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
runBlocking {
|
||||||
|
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
||||||
|
}
|
||||||
Assert.fail("This session should not be able to decrypt")
|
Assert.fail("This session should not be able to decrypt")
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
@ -222,7 +229,7 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
||||||
|
|
||||||
// Trust bob second device from Alice POV
|
// Trust bob second device from Alice POV
|
||||||
mTestHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!)
|
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!)
|
||||||
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!)
|
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
@ -38,7 +39,6 @@ import org.matrix.android.sdk.common.CommonTestHelper
|
|||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
@ -164,17 +164,22 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
|
|
||||||
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
runBlocking {
|
||||||
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||||
|
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||||
|
}
|
||||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||||
|
|
||||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
testHelper.await(latch)
|
testHelper.await(latch)
|
||||||
|
|
||||||
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
val nbOfKeys = runBlocking {
|
||||||
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
||||||
|
}
|
||||||
|
val backedUpKeys = runBlocking {
|
||||||
|
cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(2, nbOfKeys)
|
assertEquals(2, nbOfKeys)
|
||||||
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
||||||
@ -833,9 +838,12 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(1, keysBackupVersionTrust.signatures.size)
|
assertEquals(1, keysBackupVersionTrust.signatures.size)
|
||||||
|
|
||||||
val signature = keysBackupVersionTrust.signatures[0]
|
val signature = keysBackupVersionTrust.signatures[0]
|
||||||
|
val device = runBlocking {
|
||||||
|
cryptoTestData.firstSession.cryptoService().getMyCryptoDevice()
|
||||||
|
}
|
||||||
assertTrue(signature.valid)
|
assertTrue(signature.valid)
|
||||||
assertNotNull(signature.device)
|
assertNotNull(signature.device)
|
||||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
assertEquals(device.deviceId, signature.deviceId)
|
||||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
@ -1056,7 +1064,9 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertFalse(keysBackup2.isEnabled)
|
assertFalse(keysBackup2.isEnabled)
|
||||||
|
|
||||||
// - Validate the old device from the new one
|
// - Validate the old device from the new one
|
||||||
aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId)
|
testHelper.runBlockingTest {
|
||||||
|
aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
|
||||||
|
}
|
||||||
|
|
||||||
// -> Backup should automatically enable on the new device
|
// -> Backup should automatically enable on the new device
|
||||||
val latch4 = CountDownLatch(1)
|
val latch4 = CountDownLatch(1)
|
||||||
|
@ -80,7 +80,10 @@ class KeysBackupTestHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertEquals(2, cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys())
|
val totalNumbersOfBackedUpKeys = testHelper.runBlockingTest {
|
||||||
|
cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||||
|
}
|
||||||
|
Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
|
||||||
|
|
||||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||||
|
|
||||||
@ -88,15 +91,21 @@ class KeysBackupTestHelper(
|
|||||||
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||||
|
|
||||||
// Test check: aliceSession2 has no keys at login
|
// Test check: aliceSession2 has no keys at login
|
||||||
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
val inboundGroupSessionCount = testHelper.runBlockingTest {
|
||||||
|
aliceSession2.cryptoService().inboundGroupSessionsCount(false)
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, inboundGroupSessionCount)
|
||||||
|
|
||||||
// Wait for backup state to be NotTrusted
|
// Wait for backup state to be NotTrusted
|
||||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
|
|
||||||
|
val totalNumbersOfBackedUpKeysFromNewSession = testHelper.runBlockingTest {
|
||||||
|
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||||
|
}
|
||||||
return KeysBackupScenarioData(cryptoTestData,
|
return KeysBackupScenarioData(cryptoTestData,
|
||||||
aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys(),
|
totalNumbersOfBackedUpKeysFromNewSession,
|
||||||
prepareKeysBackupDataResult,
|
prepareKeysBackupDataResult,
|
||||||
aliceSession2)
|
aliceSession2)
|
||||||
}
|
}
|
||||||
@ -182,7 +191,10 @@ class KeysBackupTestHelper(
|
|||||||
Assert.assertEquals(total, imported)
|
Assert.assertEquals(total, imported)
|
||||||
|
|
||||||
// - The new device must have the same count of megolm keys
|
// - The new device must have the same count of megolm keys
|
||||||
Assert.assertEquals(testData.aliceKeys, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
val inboundGroupSessionCount = testHelper.runBlockingTest {
|
||||||
|
testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)
|
||||||
|
}
|
||||||
|
Assert.assertEquals(testData.aliceKeys, inboundGroupSessionCount)
|
||||||
|
|
||||||
// - Alice must have the same keys on both devices
|
// - Alice must have the same keys on both devices
|
||||||
//
|
//
|
||||||
|
@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.crypto.verification
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
@ -32,9 +32,7 @@ import org.junit.runners.MethodSorters
|
|||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
@ -49,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@ -75,10 +74,13 @@ class SASTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
|
val bobDevice = testHelper.runBlockingTest {
|
||||||
bobSession.myUserId,
|
bobSession.cryptoService().getMyCryptoDevice()
|
||||||
bobSession.cryptoService().getMyDevice().deviceId,
|
}
|
||||||
null)
|
val txID = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), forceDownload = true)
|
||||||
|
aliceVerificationService.beginDeviceVerification(bobSession.myUserId, bobDevice.deviceId)
|
||||||
|
}
|
||||||
assertNotNull("Alice should have a started transaction", txID)
|
assertNotNull("Alice should have a started transaction", txID)
|
||||||
|
|
||||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||||
@ -90,16 +92,13 @@ class SASTest : InstrumentedTest {
|
|||||||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||||
|
|
||||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
assertTrue(bobKeyTx is SasVerificationTransaction)
|
||||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||||
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
assertTrue(aliceKeyTx is SasVerificationTransaction)
|
||||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||||
|
|
||||||
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
|
||||||
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.state)
|
||||||
|
|
||||||
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
|
||||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
|
||||||
|
|
||||||
// Let's cancel from alice side
|
// Let's cancel from alice side
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
@ -107,7 +106,7 @@ class SASTest : InstrumentedTest {
|
|||||||
val bobListener2 = object : VerificationService.Listener {
|
val bobListener2 = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.transactionId == txID) {
|
if (tx.transactionId == txID) {
|
||||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
val immutableState = (tx as SasVerificationTransaction).state
|
||||||
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
||||||
cancelLatch.countDown()
|
cancelLatch.countDown()
|
||||||
}
|
}
|
||||||
@ -116,14 +115,16 @@ class SASTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener2)
|
bobVerificationService.addListener(bobListener2)
|
||||||
|
|
||||||
aliceSasTx.cancel(CancelCode.User)
|
testHelper.runBlockingTest {
|
||||||
|
aliceKeyTx.cancel(CancelCode.User)
|
||||||
|
}
|
||||||
testHelper.await(cancelLatch)
|
testHelper.await(cancelLatch)
|
||||||
|
|
||||||
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
|
assertTrue("Should be cancelled on alice side", aliceKeyTx.state is VerificationTxState.Cancelled)
|
||||||
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
|
assertTrue("Should be cancelled on bob side", bobKeyTx.state is VerificationTxState.Cancelled)
|
||||||
|
|
||||||
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
|
val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
|
||||||
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
|
val bobCancelState = bobKeyTx.state as VerificationTxState.Cancelled
|
||||||
|
|
||||||
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
||||||
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
||||||
@ -131,9 +132,6 @@ class SASTest : InstrumentedTest {
|
|||||||
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
||||||
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
||||||
|
|
||||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
|
||||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(testHelper)
|
cryptoTestData.cleanUp(testHelper)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,12 +175,16 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceUserID = aliceSession.myUserId
|
val aliceUserID = aliceSession.myUserId
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
val aliceDevice = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
}
|
||||||
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
|
||||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
testHelper.runBlockingTest {
|
||||||
|
tx.acceptVerification()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,7 +228,9 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceUserID = aliceSession.myUserId
|
val aliceUserID = aliceSession.myUserId
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
val aliceDevice = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
}
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
||||||
|
|
||||||
@ -267,7 +271,9 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val aliceUserID = aliceSession.myUserId
|
val aliceUserID = aliceSession.myUserId
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
val aliceDevice = testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
}
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
||||||
|
|
||||||
@ -283,12 +289,15 @@ class SASTest : InstrumentedTest {
|
|||||||
aliceUserID: String?,
|
aliceUserID: String?,
|
||||||
aliceDevice: String?,
|
aliceDevice: String?,
|
||||||
tid: String,
|
tid: String,
|
||||||
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
protocols: List<String> = emptyList(),
|
||||||
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
hashes: List<String> = emptyList(),
|
||||||
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
mac: List<String> = emptyList(),
|
||||||
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) {
|
codes: List<String> = emptyList()) {
|
||||||
|
val deviceId = runBlocking {
|
||||||
|
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
}
|
||||||
val startMessage = KeyVerificationStart(
|
val startMessage = KeyVerificationStart(
|
||||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
fromDevice = deviceId,
|
||||||
method = VerificationMethod.SAS.toValue(),
|
method = VerificationMethod.SAS.toValue(),
|
||||||
transactionId = tid,
|
transactionId = tid,
|
||||||
keyAgreementProtocols = protocols,
|
keyAgreementProtocols = protocols,
|
||||||
@ -323,16 +332,16 @@ class SASTest : InstrumentedTest {
|
|||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
|
||||||
val aliceCreatedLatch = CountDownLatch(2)
|
val aliceCreatedLatch = CountDownLatch(2)
|
||||||
val aliceCancelledLatch = CountDownLatch(2)
|
val aliceCancelledLatch = CountDownLatch(1)
|
||||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
val createdTx = mutableListOf<VerificationTransaction>()
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
createdTx.add(tx)
|
||||||
aliceCreatedLatch.countDown()
|
aliceCreatedLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
if (tx.state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
||||||
aliceCancelledLatch.countDown()
|
aliceCancelledLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,10 +349,14 @@ class SASTest : InstrumentedTest {
|
|||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobUserId = bobSession!!.myUserId
|
val bobUserId = bobSession!!.myUserId
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
val bobDeviceId = testHelper.runBlockingTest {
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
}
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), forceDownload = true)
|
||||||
|
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
|
||||||
|
aliceVerificationService.beginDeviceVerification(bobUserId, bobDeviceId)
|
||||||
|
}
|
||||||
testHelper.await(aliceCreatedLatch)
|
testHelper.await(aliceCreatedLatch)
|
||||||
testHelper.await(aliceCancelledLatch)
|
testHelper.await(aliceCancelledLatch)
|
||||||
|
|
||||||
@ -366,17 +379,10 @@ class SASTest : InstrumentedTest {
|
|||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
var accepted: ValidVerificationInfoAccept? = null
|
|
||||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
if (tx.state is VerificationTxState.OnAccepted) {
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
|
||||||
accepted = at.accepted
|
|
||||||
startReq = at.startReq
|
|
||||||
aliceAcceptedLatch.countDown()
|
aliceAcceptedLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,90 +391,62 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
|
||||||
bobVerificationService.removeListener(this)
|
bobVerificationService.removeListener(this)
|
||||||
val at = tx as IncomingSasVerificationTransaction
|
testHelper.runBlockingTest {
|
||||||
at.performAccept()
|
tx.acceptVerification()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
val bobDeviceId = runBlocking {
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
testHelper.await(aliceAcceptedLatch)
|
|
||||||
|
|
||||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
|
||||||
|
|
||||||
// check that agreement is valid
|
|
||||||
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
|
||||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
|
||||||
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
|
||||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
|
||||||
|
|
||||||
accepted!!.shortAuthenticationStrings.forEach {
|
|
||||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
|
||||||
}
|
}
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||||
|
}
|
||||||
|
testHelper.await(aliceAcceptedLatch)
|
||||||
|
|
||||||
cryptoTestData.cleanUp(testHelper)
|
cryptoTestData.cleanUp(testHelper)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_aliceAndBobSASCode() {
|
fun test_aliceAndBobSASCode() {
|
||||||
|
val supportedMethods = listOf(VerificationMethod.SAS)
|
||||||
val testHelper = CommonTestHelper(context())
|
val testHelper = CommonTestHelper(context())
|
||||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val latch = CountDownLatch(2)
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
Timber.v("Alice transactionUpdated: ${tx.state}")
|
||||||
when (uxState) {
|
latch.countDown()
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
aliceSASLatch.countDown()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
Timber.v("Bob transactionUpdated: ${tx.state}")
|
||||||
when (uxState) {
|
latch.countDown()
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
|
||||||
tx.performAccept()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
|
||||||
bobSASLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobSession.cryptoService().verificationService().addListener(bobListener)
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
|
||||||
|
}
|
||||||
|
testHelper.await(latch)
|
||||||
|
val aliceTx = aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
|
||||||
|
val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
testHelper.await(aliceSASLatch)
|
|
||||||
testHelper.await(bobSASLatch)
|
|
||||||
|
|
||||||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
|
||||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
|
||||||
|
|
||||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
|
||||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(testHelper)
|
cryptoTestData.cleanUp(testHelper)
|
||||||
}
|
}
|
||||||
@ -478,7 +456,8 @@ class SASTest : InstrumentedTest {
|
|||||||
val testHelper = CommonTestHelper(context())
|
val testHelper = CommonTestHelper(context())
|
||||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||||
|
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
@ -487,21 +466,26 @@ class SASTest : InstrumentedTest {
|
|||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
Timber.v("RequestUpdated pr=$pr")
|
||||||
|
}
|
||||||
|
|
||||||
var matchOnce = true
|
var matchOnce = true
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
Timber.v("Alice transactionUpdated: ${tx.state}")
|
||||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
if (tx !is SasVerificationTransaction) return
|
||||||
when (uxState) {
|
when (tx.state) {
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
|
||||||
tx.userHasVerifiedShortCode()
|
tx.userHasVerifiedShortCode()
|
||||||
}
|
}
|
||||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
VerificationTxState.Verified -> {
|
||||||
if (matchOnce) {
|
if (matchOnce) {
|
||||||
matchOnce = false
|
matchOnce = false
|
||||||
aliceSASLatch.countDown()
|
aliceSASLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,41 +495,53 @@ class SASTest : InstrumentedTest {
|
|||||||
val bobListener = object : VerificationService.Listener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
var acceptOnce = true
|
var acceptOnce = true
|
||||||
var matchOnce = true
|
var matchOnce = true
|
||||||
|
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
Timber.v("RequestUpdated: pr=$pr")
|
||||||
|
}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
Timber.v("Bob transactionUpdated: ${tx.state}")
|
||||||
Log.v("TEST", "== bobState ${uxState.name}")
|
if (tx !is SasVerificationTransaction) return
|
||||||
when (uxState) {
|
when (tx.state) {
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
VerificationTxState.OnStarted -> testHelper.runBlockingTest {
|
||||||
if (acceptOnce) {
|
if (acceptOnce) {
|
||||||
acceptOnce = false
|
acceptOnce = false
|
||||||
tx.performAccept()
|
tx.acceptVerification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
|
||||||
if (matchOnce) {
|
if (matchOnce) {
|
||||||
matchOnce = false
|
matchOnce = false
|
||||||
tx.userHasVerifiedShortCode()
|
tx.userHasVerifiedShortCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
VerificationTxState.ShortCodeAccepted -> {
|
||||||
bobSASLatch.countDown()
|
bobSASLatch.countDown()
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
val bobDeviceId = runBlocking {
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
}
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
|
||||||
|
}
|
||||||
testHelper.await(aliceSASLatch)
|
testHelper.await(aliceSASLatch)
|
||||||
testHelper.await(bobSASLatch)
|
testHelper.await(bobSASLatch)
|
||||||
|
|
||||||
// Assert that devices are verified
|
// Assert that devices are verified
|
||||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
|
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
|
||||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
|
||||||
|
}
|
||||||
|
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
|
||||||
|
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
|
||||||
|
}
|
||||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||||
cryptoTestData.cleanUp(testHelper)
|
cryptoTestData.cleanUp(testHelper)
|
||||||
@ -563,11 +559,13 @@ class SASTest : InstrumentedTest {
|
|||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
val req = testHelper.runBlockingTest {
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
bobSession.myUserId,
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
cryptoTestData.roomId
|
bobSession.myUserId,
|
||||||
)
|
cryptoTestData.roomId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var requestID: String? = null
|
var requestID: String? = null
|
||||||
|
|
||||||
@ -590,11 +588,13 @@ class SASTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bobVerificationService.readyPendingVerification(
|
testHelper.runBlockingTest {
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
bobVerificationService.readyPendingVerification(
|
||||||
aliceSession.myUserId,
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
requestID!!
|
aliceSession.myUserId,
|
||||||
)
|
requestID!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// wait for alice to get the ready
|
// wait for alice to get the ready
|
||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
@ -606,19 +606,19 @@ class SASTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start concurrent!
|
// Start concurrent!
|
||||||
aliceVerificationService.beginKeyVerificationInDMs(
|
testHelper.runBlockingTest {
|
||||||
VerificationMethod.SAS,
|
aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
requestID!!,
|
methods = listOf(VerificationMethod.SAS),
|
||||||
cryptoTestData.roomId,
|
otherUserId = bobSession.myUserId,
|
||||||
bobSession.myUserId,
|
roomId = cryptoTestData.roomId
|
||||||
bobSession.sessionParams.deviceId!!)
|
)
|
||||||
|
|
||||||
bobVerificationService.beginKeyVerificationInDMs(
|
bobVerificationService.requestKeyVerificationInDMs(
|
||||||
VerificationMethod.SAS,
|
methods = listOf(VerificationMethod.SAS),
|
||||||
requestID!!,
|
otherUserId = aliceSession.myUserId,
|
||||||
cryptoTestData.roomId,
|
roomId = cryptoTestData.roomId
|
||||||
aliceSession.myUserId,
|
)
|
||||||
aliceSession.sessionParams.deviceId!!)
|
}
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
// we should reach SHOW SAS on both
|
||||||
var alicePovTx: SasVerificationTransaction?
|
var alicePovTx: SasVerificationTransaction?
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
@ -23,19 +23,13 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
|
||||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import kotlin.coroutines.Continuation
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@ -147,50 +141,19 @@ class VerificationTest : InstrumentedTest {
|
|||||||
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO Add tests without SAS
|
|
||||||
|
|
||||||
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
|
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
|
||||||
bobSupportedMethods: List<VerificationMethod>,
|
bobSupportedMethods: List<VerificationMethod>,
|
||||||
expectedResultForAlice: ExpectedResult,
|
expectedResultForAlice: ExpectedResult,
|
||||||
expectedResultForBob: ExpectedResult) {
|
expectedResultForBob: ExpectedResult) {
|
||||||
val testHelper = CommonTestHelper(context())
|
val testHelper = CommonTestHelper(context())
|
||||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
testHelper.doSync<Unit> { callback ->
|
cryptoTestHelper.initializeCrossSigning(aliceSession)
|
||||||
aliceSession.cryptoService().crossSigningService()
|
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||||
.initializeCrossSigning(
|
|
||||||
object : UserInteractiveAuthInterceptor {
|
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
|
||||||
promise.resume(
|
|
||||||
UserPasswordAuth(
|
|
||||||
user = aliceSession.myUserId,
|
|
||||||
password = TestConstants.PASSWORD,
|
|
||||||
session = flowResponse.session
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
testHelper.doSync<Unit> { callback ->
|
|
||||||
bobSession.cryptoService().crossSigningService()
|
|
||||||
.initializeCrossSigning(
|
|
||||||
object : UserInteractiveAuthInterceptor {
|
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
|
||||||
promise.resume(
|
|
||||||
UserPasswordAuth(
|
|
||||||
user = bobSession.myUserId,
|
|
||||||
password = TestConstants.PASSWORD,
|
|
||||||
session = flowResponse.session
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
val bobVerificationService = bobSession.cryptoService().verificationService()
|
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||||
@ -202,6 +165,7 @@ class VerificationTest : InstrumentedTest {
|
|||||||
val aliceListener = object : VerificationService.Listener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
// Step 4: Alice receive the ready request
|
// Step 4: Alice receive the ready request
|
||||||
|
Timber.v("Alice is ready: ${pr.isReady}")
|
||||||
if (pr.isReady) {
|
if (pr.isReady) {
|
||||||
aliceReadyPendingVerificationRequest = pr
|
aliceReadyPendingVerificationRequest = pr
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
@ -213,16 +177,19 @@ class VerificationTest : InstrumentedTest {
|
|||||||
val bobListener = object : VerificationService.Listener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
// Step 2: Bob accepts the verification request
|
// Step 2: Bob accepts the verification request
|
||||||
bobVerificationService.readyPendingVerificationInDMs(
|
Timber.v("Bob accepts the verification request")
|
||||||
bobSupportedMethods,
|
testHelper.runBlockingTest {
|
||||||
aliceSession.myUserId,
|
bobVerificationService.readyPendingVerification(
|
||||||
cryptoTestData.roomId,
|
bobSupportedMethods,
|
||||||
pr.transactionId!!
|
aliceSession.myUserId,
|
||||||
)
|
pr.transactionId!!
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
// Step 3: Bob is ready
|
// Step 3: Bob is ready
|
||||||
|
Timber.v("Bob is ready: ${pr.isReady}")
|
||||||
if (pr.isReady) {
|
if (pr.isReady) {
|
||||||
bobReadyPendingVerificationRequest = pr
|
bobReadyPendingVerificationRequest = pr
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
@ -233,7 +200,9 @@ class VerificationTest : InstrumentedTest {
|
|||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
// Step 1: Alice starts a verification request
|
// Step 1: Alice starts a verification request
|
||||||
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
testHelper.runBlockingTest {
|
||||||
|
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
||||||
|
}
|
||||||
testHelper.await(latch)
|
testHelper.await(latch)
|
||||||
|
|
||||||
aliceReadyPendingVerificationRequest!!.let { pr ->
|
aliceReadyPendingVerificationRequest!!.let { pr ->
|
@ -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 android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
@ -32,14 +32,12 @@ import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
|||||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.NewSessionListener
|
import org.matrix.android.sdk.internal.crypto.NewSessionListener
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
|
|
||||||
@ -49,9 +47,9 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun keysBackupService(): KeysBackupService
|
fun keysBackupService(): KeysBackupService
|
||||||
|
|
||||||
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
suspend fun setDeviceName(deviceId: String, deviceName: String)
|
||||||
|
|
||||||
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
|
suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
|
||||||
|
|
||||||
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
||||||
|
|
||||||
@ -61,11 +59,9 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun setWarnOnUnknownDevices(warn: Boolean)
|
fun setWarnOnUnknownDevices(warn: Boolean)
|
||||||
|
|
||||||
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
|
suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
||||||
|
|
||||||
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
suspend fun getMyCryptoDevice(): CryptoDeviceInfo
|
||||||
|
|
||||||
fun getMyDevice(): CryptoDeviceInfo
|
|
||||||
|
|
||||||
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
||||||
|
|
||||||
@ -73,8 +69,6 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
|
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
|
||||||
|
|
||||||
fun getDeviceTrackingStatus(userId: String): Int
|
|
||||||
|
|
||||||
suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
|
suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
|
||||||
password: String,
|
password: String,
|
||||||
progressListener: ProgressListener?): ImportRoomKeysResult
|
progressListener: ProgressListener?): ImportRoomKeysResult
|
||||||
@ -83,7 +77,7 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
||||||
|
|
||||||
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||||
|
|
||||||
fun reRequestRoomKeyForEvent(event: Event)
|
fun reRequestRoomKeyForEvent(event: Event)
|
||||||
|
|
||||||
@ -91,29 +85,26 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||||
|
|
||||||
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
suspend fun fetchDevicesList(): List<DeviceInfo>
|
||||||
|
|
||||||
fun getMyDevicesInfo(): List<DeviceInfo>
|
fun getMyDevicesInfo(): List<DeviceInfo>
|
||||||
|
|
||||||
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
|
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
|
||||||
|
|
||||||
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo
|
||||||
|
|
||||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||||
|
|
||||||
fun isRoomEncrypted(roomId: String): Boolean
|
fun isRoomEncrypted(roomId: String): Boolean
|
||||||
|
|
||||||
fun encryptEventContent(eventContent: Content,
|
suspend fun encryptEventContent(eventContent: Content,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String): MXEncryptEventContentResult
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
|
||||||
|
|
||||||
fun discardOutboundSession(roomId: String)
|
fun discardOutboundSession(roomId: String)
|
||||||
|
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||||
|
|
||||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
|
||||||
|
|
||||||
fun getEncryptionAlgorithm(roomId: String): String?
|
fun getEncryptionAlgorithm(roomId: String): String?
|
||||||
|
|
||||||
@ -121,11 +112,11 @@ interface CryptoService {
|
|||||||
|
|
||||||
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo>
|
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo>
|
||||||
|
|
||||||
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo>
|
||||||
|
|
||||||
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
|
fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||||
|
|
||||||
@ -150,7 +141,7 @@ interface CryptoService {
|
|||||||
* Perform any background tasks that can be done before a message is ready to
|
* Perform any background tasks that can be done before a message is ready to
|
||||||
* send, in order to speed up sending of the message.
|
* send, in order to speed up sending of the message.
|
||||||
*/
|
*/
|
||||||
fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>)
|
suspend fun prepareToEncrypt(roomId: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When LL all room members might not be loaded when setting up encryption.
|
* When LL all room members might not be loaded when setting up encryption.
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto.crosssigning
|
package org.matrix.android.sdk.api.session.crypto.crosssigning
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
@ -29,31 +28,30 @@ interface CrossSigningService {
|
|||||||
/**
|
/**
|
||||||
* Is our own device signed by our own cross signing identity
|
* Is our own device signed by our own cross signing identity
|
||||||
*/
|
*/
|
||||||
fun isCrossSigningVerified(): Boolean
|
suspend fun isCrossSigningVerified(): Boolean
|
||||||
|
|
||||||
// TODO this isn't used anywhere besides in tests?
|
// TODO this isn't used anywhere besides in tests?
|
||||||
// Is this the local trust concept that we have for devices?
|
// Is this the local trust concept that we have for devices?
|
||||||
fun isUserTrusted(otherUserId: String): Boolean
|
suspend fun isUserTrusted(otherUserId: String): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will not force a download of the key, but will verify signatures trust chain.
|
* Will not force a download of the key, but will verify signatures trust chain.
|
||||||
* Checks that my trusted user key has signed the other user UserKey
|
* Checks that my trusted user key has signed the other user UserKey
|
||||||
*/
|
*/
|
||||||
fun checkUserTrust(otherUserId: String): UserTrustResult
|
suspend fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize cross signing for this user.
|
* Initialize cross signing for this user.
|
||||||
* Users needs to enter credentials
|
* Users needs to enter credentials
|
||||||
*/
|
*/
|
||||||
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?,
|
suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does our own user have a valid cross signing identity uploaded.
|
* Does our own user have a valid cross signing identity uploaded.
|
||||||
*
|
*
|
||||||
* In other words has any of our devices uploaded public cross signing keys to the server.
|
* In other words has any of our devices uploaded public cross signing keys to the server.
|
||||||
*/
|
*/
|
||||||
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
suspend fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject the private cross signing keys, likely from backup, into our store.
|
* Inject the private cross signing keys, likely from backup, into our store.
|
||||||
@ -62,25 +60,25 @@ interface CrossSigningService {
|
|||||||
* by the server and if they do so
|
* by the server and if they do so
|
||||||
*/
|
*/
|
||||||
suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||||
uskKeyPrivateKey: String?,
|
uskKeyPrivateKey: String?,
|
||||||
sskPrivateKey: String?): UserTrustResult
|
sskPrivateKey: String?): UserTrustResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the public cross signing keys for the given user
|
* Get the public cross signing keys for the given user
|
||||||
*
|
*
|
||||||
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
|
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
|
||||||
*/
|
*/
|
||||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||||
|
|
||||||
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>>
|
||||||
|
|
||||||
/** Get our own public cross signing keys */
|
/** Get our own public cross signing keys */
|
||||||
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||||
|
|
||||||
/** Get our own private cross signing keys */
|
/** Get our own private cross signing keys */
|
||||||
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||||
|
|
||||||
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
|
fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can we sign our other devices or other users?
|
* Can we sign our other devices or other users?
|
||||||
@ -93,11 +91,10 @@ interface CrossSigningService {
|
|||||||
fun allPrivateKeysKnown(): Boolean
|
fun allPrivateKeysKnown(): Boolean
|
||||||
|
|
||||||
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
||||||
fun trustUser(otherUserId: String,
|
suspend fun trustUser(otherUserId: String)
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
/** Mark our own master key as trusted */
|
/** Mark our own master key as trusted */
|
||||||
fun markMyMasterKeyAsTrusted()
|
suspend fun markMyMasterKeyAsTrusted()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign one of your devices and upload the signature
|
* Sign one of your devices and upload the signature
|
||||||
@ -114,10 +111,10 @@ interface CrossSigningService {
|
|||||||
* using the self-signing key for our own devices or using the user-signing key and the master
|
* using the self-signing key for our own devices or using the user-signing key and the master
|
||||||
* key of another user.
|
* key of another user.
|
||||||
*/
|
*/
|
||||||
fun checkDeviceTrust(otherUserId: String,
|
suspend fun checkDeviceTrust(otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String,
|
||||||
// TODO what is locallyTrusted used for?
|
// TODO what is locallyTrusted used for?
|
||||||
locallyTrusted: Boolean?): DeviceTrustResult
|
locallyTrusted: Boolean?): DeviceTrustResult
|
||||||
|
|
||||||
// FIXME Those method do not have to be in the service
|
// FIXME Those method do not have to be in the service
|
||||||
// TODO those three methods doesn't seem to be used anywhere?
|
// TODO those three methods doesn't seem to be used anywhere?
|
||||||
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
|||||||
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||||
|
|
||||||
interface KeysBackupService {
|
interface KeysBackupService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the current version of the backup from the homeserver
|
* Retrieve the current version of the backup from the homeserver
|
||||||
*
|
*
|
||||||
@ -45,12 +46,12 @@ interface KeysBackupService {
|
|||||||
/**
|
/**
|
||||||
* Facility method to get the total number of locally stored keys
|
* Facility method to get the total number of locally stored keys
|
||||||
*/
|
*/
|
||||||
fun getTotalNumbersOfKeys(): Int
|
suspend fun getTotalNumbersOfKeys(): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facility method to get the number of backed up keys
|
* Facility method to get the number of backed up keys
|
||||||
*/
|
*/
|
||||||
fun getTotalNumbersOfBackedUpKeys(): Int
|
suspend fun getTotalNumbersOfBackedUpKeys(): Int
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Start to back up keys immediately.
|
// * Start to back up keys immediately.
|
||||||
@ -71,7 +72,7 @@ interface KeysBackupService {
|
|||||||
/**
|
/**
|
||||||
* Return the current progress of the backup
|
* Return the current progress of the backup
|
||||||
*/
|
*/
|
||||||
fun getBackupProgress(progressListener: ProgressListener)
|
suspend fun getBackupProgress(progressListener: ProgressListener)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about a backup version defined on the homeserver.
|
* Get information about a backup version defined on the homeserver.
|
||||||
@ -128,7 +129,7 @@ interface KeysBackupService {
|
|||||||
* Ask if the backup on the server contains keys that we may do not have locally.
|
* Ask if the backup on the server contains keys that we may do not have locally.
|
||||||
* This should be called when entering in the state READY_TO_BACKUP
|
* This should be called when entering in the state READY_TO_BACKUP
|
||||||
*/
|
*/
|
||||||
fun canRestoreKeys(): Boolean
|
suspend fun canRestoreKeys(): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set trust on a keys backup version.
|
* Set trust on a keys backup version.
|
||||||
@ -199,7 +200,7 @@ interface KeysBackupService {
|
|||||||
|
|
||||||
// For gossiping
|
// For gossiping
|
||||||
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||||
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||||
|
|
||||||
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
|
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
|
||||||
}
|
}
|
||||||
|
@ -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
|
* Call when you have scan the other user QR code
|
||||||
*/
|
*/
|
||||||
fun userHasScannedOtherQrCode(otherQrCodeText: String)
|
suspend fun userHasScannedOtherQrCode(otherQrCodeText: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call when you confirm that other user has scanned your QR code
|
* Call when you confirm that other user has scanned your QR code
|
||||||
*/
|
*/
|
||||||
fun otherUserScannedMyQrCode()
|
suspend fun otherUserScannedMyQrCode()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call when you do not confirm that other user has scanned your QR code
|
* Call when you do not confirm that other user has scanned your QR code
|
||||||
*/
|
*/
|
||||||
fun otherUserDidNotScannedMyQrCode()
|
suspend fun otherUserDidNotScannedMyQrCode()
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,6 @@ interface SasVerificationTransaction : VerificationTransaction {
|
|||||||
|
|
||||||
fun supportsEmoji(): Boolean
|
fun supportsEmoji(): Boolean
|
||||||
|
|
||||||
fun supportsDecimal(): Boolean
|
|
||||||
|
|
||||||
fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
|
fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
|
||||||
|
|
||||||
fun getDecimalCodeRepresentation(): String
|
fun getDecimalCodeRepresentation(): String
|
||||||
@ -30,9 +28,9 @@ interface SasVerificationTransaction : VerificationTransaction {
|
|||||||
* To be called by the client when the user has verified that
|
* To be called by the client when the user has verified that
|
||||||
* both short codes do match
|
* both short codes do match
|
||||||
*/
|
*/
|
||||||
fun userHasVerifiedShortCode()
|
suspend fun userHasVerifiedShortCode()
|
||||||
|
|
||||||
fun acceptVerification()
|
suspend fun acceptVerification()
|
||||||
|
|
||||||
fun shortCodeDoesNotMatch()
|
suspend fun shortCodeDoesNotMatch()
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto.verification
|
package org.matrix.android.sdk.api.session.crypto.verification
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +35,7 @@ interface VerificationService {
|
|||||||
/**
|
/**
|
||||||
* Mark this device as verified manually
|
* Mark this device as verified manually
|
||||||
*/
|
*/
|
||||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||||
|
|
||||||
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
|
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
|
||||||
|
|
||||||
@ -46,54 +45,37 @@ interface VerificationService {
|
|||||||
|
|
||||||
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
|
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
|
||||||
|
|
||||||
fun beginKeyVerification(method: VerificationMethod,
|
/**
|
||||||
otherUserId: String,
|
* Request key verification with another user via room events (instead of the to-device API).
|
||||||
otherDeviceId: String,
|
*/
|
||||||
transactionId: String?): String?
|
suspend fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
roomId: String,
|
||||||
|
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request key verification with another user via room events (instead of the to-device API)
|
* Request a self key verification using to-device API (instead of room events).
|
||||||
*/
|
*/
|
||||||
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
|
suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest
|
||||||
otherUserId: String,
|
|
||||||
roomId: String,
|
|
||||||
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
|
||||||
|
|
||||||
fun cancelVerificationRequest(request: PendingVerificationRequest)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a key verification from another user using toDevice events.
|
* You should call this method after receiving a verification request.
|
||||||
|
* Accept the verification request advertising the given methods as supported
|
||||||
|
* Returns false if the request is unknown or transaction is not ready.
|
||||||
*/
|
*/
|
||||||
fun requestKeyVerification(methods: List<VerificationMethod>,
|
suspend fun readyPendingVerification(methods: List<VerificationMethod>,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDevices: List<String>?): PendingVerificationRequest
|
transactionId: String): Boolean
|
||||||
|
|
||||||
fun declineVerificationRequestInDMs(otherUserId: String,
|
suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
|
||||||
transactionId: String,
|
|
||||||
roomId: String)
|
|
||||||
|
|
||||||
// Only SAS method is supported for the moment
|
suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String)
|
||||||
// TODO Parameter otherDeviceId should be removed in this case
|
|
||||||
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
|
||||||
transactionId: String,
|
|
||||||
roomId: String,
|
|
||||||
otherUserId: String,
|
|
||||||
otherDeviceId: String): String
|
|
||||||
|
|
||||||
/**
|
suspend fun beginKeyVerification(method: VerificationMethod,
|
||||||
* Returns false if the request is unknown
|
otherUserId: String,
|
||||||
*/
|
transactionId: String): String?
|
||||||
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
|
|
||||||
otherUserId: String,
|
|
||||||
roomId: String,
|
|
||||||
transactionId: String): Boolean
|
|
||||||
|
|
||||||
/**
|
suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String?
|
||||||
* Returns false if the request is unknown
|
|
||||||
*/
|
|
||||||
fun readyPendingVerification(methods: List<VerificationMethod>,
|
|
||||||
otherUserId: String,
|
|
||||||
transactionId: String): Boolean
|
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
/**
|
/**
|
||||||
@ -137,6 +119,4 @@ interface VerificationService {
|
|||||||
return age in tooInThePast..tooInTheFuture
|
return age in tooInThePast..tooInTheFuture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
|
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,9 @@ interface VerificationTransaction {
|
|||||||
/**
|
/**
|
||||||
* User wants to cancel the transaction
|
* User wants to cancel the transaction
|
||||||
*/
|
*/
|
||||||
fun cancel()
|
suspend fun cancel()
|
||||||
|
|
||||||
fun cancel(code: CancelCode)
|
suspend fun cancel(code: CancelCode)
|
||||||
|
|
||||||
fun isToDeviceTransport(): Boolean
|
fun isToDeviceTransport(): Boolean
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ object EventType {
|
|||||||
const val SEND_SECRET = "m.secret.send"
|
const val SEND_SECRET = "m.secret.send"
|
||||||
|
|
||||||
// Interactive key verification
|
// Interactive key verification
|
||||||
|
const val KEY_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||||
const val KEY_VERIFICATION_START = "m.key.verification.start"
|
const val KEY_VERIFICATION_START = "m.key.verification.start"
|
||||||
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"
|
const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept"
|
||||||
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.model.message
|
package org.matrix.android.sdk.api.session.room.model.message
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
|
||||||
object MessageType {
|
object MessageType {
|
||||||
const val MSGTYPE_TEXT = "m.text"
|
const val MSGTYPE_TEXT = "m.text"
|
||||||
const val MSGTYPE_EMOTE = "m.emote"
|
const val MSGTYPE_EMOTE = "m.emote"
|
||||||
@ -26,7 +28,7 @@ object MessageType {
|
|||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
|
|
||||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
const val MSGTYPE_VERIFICATION_REQUEST = EventType.KEY_VERIFICATION_REQUEST
|
||||||
|
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
|
@ -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 androidx.paging.PagedList
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.cancelChildren
|
import kotlinx.coroutines.cancelChildren
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
@ -55,7 +54,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
|||||||
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
|
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
|
||||||
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
||||||
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
|
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
@ -65,8 +63,9 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
|
||||||
@ -76,14 +75,9 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
|||||||
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
|
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskThread
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uniffi.olm.Request
|
import uniffi.olm.Request
|
||||||
import uniffi.olm.RequestType
|
import uniffi.olm.RequestType
|
||||||
@ -130,9 +124,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
private val sender: RequestSender,
|
private val requestSender: RequestSender,
|
||||||
private val crossSigningService: CrossSigningService,
|
private val crossSigningService: CrossSigningService,
|
||||||
private val verificationService: RustVerificationService,
|
private val verificationService: RustVerificationService,
|
||||||
private val keysBackupService: RustKeyBackupService,
|
private val keysBackupService: RustKeyBackupService,
|
||||||
@ -153,7 +146,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
// Locks for some of our operations
|
// Locks for some of our operations
|
||||||
private val keyClaimLock: Mutex = Mutex()
|
private val keyClaimLock: Mutex = Mutex()
|
||||||
private val outgoingRequestsLock: Mutex = Mutex()
|
private val outgoingRequestsProcessor = OutgoingRequestsProcessor(
|
||||||
|
requestSender = requestSender,
|
||||||
|
coroutineScope = cryptoCoroutineScope,
|
||||||
|
cryptoSessionInfoProvider = cryptoSessionInfoProvider,
|
||||||
|
shieldComputer = crossSigningService::shieldForGroup
|
||||||
|
)
|
||||||
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap()
|
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap()
|
||||||
|
|
||||||
fun onStateEvent(roomId: String, event: Event) {
|
fun onStateEvent(roomId: String, event: Event) {
|
||||||
@ -166,51 +164,33 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
fun onLiveEvent(roomId: String, event: Event) {
|
fun onLiveEvent(roomId: String, event: Event) {
|
||||||
if (event.isStateEvent()) {
|
if (event.isStateEvent()) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
else -> cryptoCoroutineScope.launch {
|
}
|
||||||
this@DefaultCryptoService.verificationService.onEvent(event)
|
} else {
|
||||||
|
cryptoCoroutineScope.launch {
|
||||||
|
verificationService.onEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val gossipingBuffer = mutableListOf<Event>()
|
private val gossipingBuffer = mutableListOf<Event>()
|
||||||
|
|
||||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
override suspend fun setDeviceName(deviceId: String, deviceName: String) {
|
||||||
setDeviceNameTask
|
val params = SetDeviceNameTask.Params(deviceId, deviceName)
|
||||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
setDeviceNameTask.execute(params)
|
||||||
this.executionThread = TaskThread.CRYPTO
|
try {
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
downloadKeys(listOf(userId), true)
|
||||||
override fun onSuccess(data: Unit) {
|
} catch (failure: Throwable) {
|
||||||
// bg refresh of crypto device
|
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
|
||||||
cryptoCoroutineScope.launch {
|
}
|
||||||
try {
|
|
||||||
downloadKeys(listOf(userId), true)
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
|
override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||||
deleteDeviceTask
|
val params = DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)
|
||||||
.configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) {
|
deleteDeviceTask.execute(params)
|
||||||
this.executionThread = TaskThread.CRYPTO
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
||||||
@ -218,27 +198,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return if (longFormat) "Rust SDK 0.3" else "0.3"
|
return if (longFormat) "Rust SDK 0.3" else "0.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMyDevice(): CryptoDeviceInfo {
|
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo {
|
||||||
return runBlocking { olmMachine.ownDevice() }
|
return olmMachine.ownDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
override suspend fun fetchDevicesList(): List<DeviceInfo> {
|
||||||
getDevicesTask
|
val devicesList = tryOrNull {
|
||||||
.configureWith {
|
getDevicesTask.execute(Unit).devices
|
||||||
// this.executionThread = TaskThread.CRYPTO
|
}.orEmpty()
|
||||||
this.callback = object : MatrixCallback<DevicesListResponse> {
|
cryptoStore.saveMyDevicesInfo(devicesList)
|
||||||
override fun onFailure(failure: Throwable) {
|
return devicesList
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSuccess(data: DevicesListResponse) {
|
|
||||||
// Save in local DB
|
|
||||||
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
|
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
|
||||||
@ -249,16 +218,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return cryptoStore.getMyDevicesInfo()
|
return cryptoStore.getMyDevicesInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
|
||||||
getDeviceInfoTask
|
val params = GetDeviceInfoTask.Params(deviceId)
|
||||||
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
return getDeviceInfoTask.execute(params)
|
||||||
this.executionThread = TaskThread.CRYPTO
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return if (onlyBackedUp) {
|
return if (onlyBackedUp) {
|
||||||
keysBackupService.getTotalNumbersOfBackedUpKeys()
|
keysBackupService.getTotalNumbersOfBackedUpKeys()
|
||||||
} else {
|
} else {
|
||||||
@ -267,20 +232,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
// return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
// return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the tracking status
|
|
||||||
*
|
|
||||||
* @param userId the user id
|
|
||||||
* @return the tracking status
|
|
||||||
*/
|
|
||||||
override fun getDeviceTrackingStatus(userId: String): Int {
|
|
||||||
return if (olmMachine.isUserTracked(userId)) {
|
|
||||||
3
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell if the MXCrypto is started
|
* Tell if the MXCrypto is started
|
||||||
*
|
*
|
||||||
@ -299,29 +250,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun start() {
|
fun start() {
|
||||||
internalStart()
|
internalStart()
|
||||||
// Just update
|
cryptoCoroutineScope.launch {
|
||||||
fetchDevicesList(NoOpMatrixCallback())
|
// Just update
|
||||||
|
fetchDevicesList()
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
|
||||||
cryptoStore.tidyUpDataBase()
|
cryptoStore.tidyUpDataBase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureDevice() {
|
|
||||||
cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
|
|
||||||
// Open the store
|
|
||||||
cryptoStore.open()
|
|
||||||
|
|
||||||
// this can throw if no backup
|
|
||||||
/*
|
|
||||||
TODO
|
|
||||||
tryOrNull {
|
|
||||||
keysBackupService.checkAndStartKeysBackup()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun internalStart() {
|
private fun internalStart() {
|
||||||
if (isStarted.get() || isStarting.get()) {
|
if (isStarted.get() || isStarting.get()) {
|
||||||
return
|
return
|
||||||
@ -355,9 +290,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Close the crypto
|
* Close the crypto
|
||||||
*/
|
*/
|
||||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
fun close() {
|
||||||
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
||||||
cryptoStore.close()
|
cryptoCoroutineScope.launch {
|
||||||
|
withContext(coroutineDispatchers.crypto + NonCancellable) {
|
||||||
|
cryptoStore.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always enabled on Matrix Android SDK2
|
// Always enabled on Matrix Android SDK2
|
||||||
@ -380,7 +319,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
suspend fun onSyncCompleted() {
|
suspend fun onSyncCompleted() {
|
||||||
if (isStarted()) {
|
if (isStarted()) {
|
||||||
sendOutgoingRequests()
|
outgoingRequestsProcessor.process(olmMachine)
|
||||||
// This isn't a copy paste error. Sending the outgoing requests may
|
// This isn't a copy paste error. Sending the outgoing requests may
|
||||||
// claim one-time keys and establish 1-to-1 Olm sessions with devices, while some
|
// claim one-time keys and establish 1-to-1 Olm sessions with devices, while some
|
||||||
// outgoing requests are waiting for an Olm session to be established (e.g. forwarding
|
// outgoing requests are waiting for an Olm session to be established (e.g. forwarding
|
||||||
@ -389,7 +328,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
// The second call sends out those requests that are waiting for the
|
// The second call sends out those requests that are waiting for the
|
||||||
// keys claim request to be sent out.
|
// keys claim request to be sent out.
|
||||||
// This could be omitted but then devices might be waiting for the next
|
// This could be omitted but then devices might be waiting for the next
|
||||||
sendOutgoingRequests()
|
outgoingRequestsProcessor.process(olmMachine)
|
||||||
|
|
||||||
keysBackupService.maybeBackupKeys()
|
keysBackupService.maybeBackupKeys()
|
||||||
}
|
}
|
||||||
@ -410,41 +349,19 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
*/
|
*/
|
||||||
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
||||||
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
if (userId.isEmpty() || deviceId.isNullOrEmpty()) return null
|
||||||
runBlocking {
|
return olmMachine.getCryptoDeviceInfo(userId, deviceId)
|
||||||
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId, deviceId)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo> {
|
||||||
return runBlocking {
|
return olmMachine.getCryptoDeviceInfo(userId)
|
||||||
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfoList(userId: String) = getLiveCryptoDeviceInfoList(listOf(userId))
|
||||||
return getLiveCryptoDeviceInfo(listOf(userId))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
|
||||||
return runBlocking {
|
return olmMachine.getLiveDevices(userIds)
|
||||||
this@DefaultCryptoService.olmMachine.getLiveDevices(userIds) // ?: LiveDevice(userIds, deviceObserver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the blocked/verified state of the given device.
|
|
||||||
*
|
|
||||||
* @param trustLevel the new trust level
|
|
||||||
* @param userId the owner of the device
|
|
||||||
* @param deviceId the unique identifier for the device.
|
|
||||||
*/
|
|
||||||
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -503,8 +420,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* @return the stored device keys for a user.
|
* @return the stored device keys for a user.
|
||||||
*/
|
*/
|
||||||
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
override suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||||
return this.getCryptoDeviceInfo(userId).toMutableList()
|
return this.getCryptoDeviceInfoList(userId).toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||||
@ -533,34 +450,29 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param eventContent the content of the event.
|
* @param eventContent the content of the event.
|
||||||
* @param eventType the type of the event.
|
* @param eventType the type of the event.
|
||||||
* @param roomId the room identifier the event will be sent.
|
* @param roomId the room identifier the event will be sent.
|
||||||
* @param callback the asynchronous callback
|
|
||||||
*/
|
*/
|
||||||
override fun encryptEventContent(eventContent: Content,
|
override suspend fun encryptEventContent(eventContent: Content,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String): MXEncryptEventContentResult {
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
|
||||||
// moved to crypto scope to have up to date values
|
// moved to crypto scope to have up to date values
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
val algorithm = getEncryptionAlgorithm(roomId)
|
val algorithm = getEncryptionAlgorithm(roomId)
|
||||||
|
|
||||||
if (algorithm != null) {
|
if (algorithm != null) {
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
|
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
|
||||||
runCatching {
|
measureTimeMillis {
|
||||||
measureTimeMillis {
|
preshareRoomKey(roomId, userIds)
|
||||||
preshareRoomKey(roomId, userIds)
|
}.also {
|
||||||
}.also {
|
Timber.d("Shared room key in room $roomId took $it ms")
|
||||||
Timber.d("Shared room key in room $roomId took $it ms")
|
}
|
||||||
}
|
val content = encrypt(roomId, eventType, eventContent)
|
||||||
val content = encrypt(roomId, eventType, eventContent)
|
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
|
||||||
}.foldToCallback(callback)
|
|
||||||
} else {
|
} else {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||||
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
|
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
|
||||||
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,22 +489,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @return the MXEventDecryptionResult data, or throw in case of error
|
* @return the MXEventDecryptionResult data, or throw in case of error
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
return runBlocking {
|
return olmMachine.decryptRoomEvent(event)
|
||||||
olmMachine.decryptRoomEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt an event asynchronously
|
|
||||||
*
|
|
||||||
* @param event the raw event.
|
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
|
||||||
* @param callback the callback to return data or null
|
|
||||||
*/
|
|
||||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
|
||||||
// This isn't really used anywhere, maybe just remove it?
|
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -616,7 +514,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
// Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
// Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
||||||
// } finally {
|
// } finally {
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
olmMachine.updateTrackedUsers(userIds)
|
|
||||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds)
|
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
@ -721,7 +618,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
this.keysBackupService.onSecretKeyGossip(secretContent.secretValue)
|
this.keysBackupService.onSecretKeyGossip(secretContent.secretValue)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
this.verificationService.onEvent(event)
|
this.verificationService.onEvent(null, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
liveEventManager.get().dispatchOnLiveToDevice(event)
|
liveEventManager.get().dispatchOnLiveToDevice(event)
|
||||||
@ -730,26 +627,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>) {
|
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>) {
|
||||||
keyClaimLock.withLock {
|
claimMissingKeys(roomMembers)
|
||||||
val request = this.olmMachine.getMissingSessions(roomMembers)
|
val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() }
|
||||||
// This request can only be a keys claim request.
|
|
||||||
if (request != null) {
|
|
||||||
when (request) {
|
|
||||||
is Request.KeysClaim -> {
|
|
||||||
claimKeys(request)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val keyShareLock = roomKeyShareLocks.getOrPut(roomId, { Mutex() })
|
|
||||||
var sharedKey = false
|
var sharedKey = false
|
||||||
|
|
||||||
keyShareLock.withLock {
|
keyShareLock.withLock {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
this@DefaultCryptoService.olmMachine.shareRoomKey(roomId, roomMembers).map {
|
olmMachine.shareRoomKey(roomId, roomMembers).map {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Request.ToDevice -> {
|
is Request.ToDevice -> {
|
||||||
sharedKey = true
|
sharedKey = true
|
||||||
@ -775,19 +658,35 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun claimMissingKeys(roomMembers: List<String>) = keyClaimLock.withLock {
|
||||||
|
val request = this.olmMachine.getMissingSessions(roomMembers)
|
||||||
|
// This request can only be a keys claim request.
|
||||||
|
when (request) {
|
||||||
|
is Request.KeysClaim -> {
|
||||||
|
claimKeys(request)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun encrypt(roomId: String, eventType: String, content: Content): Content {
|
private suspend fun encrypt(roomId: String, eventType: String, content: Content): Content {
|
||||||
return olmMachine.encrypt(roomId, eventType, content)
|
return olmMachine.encrypt(roomId, eventType, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun uploadKeys(request: Request.KeysUpload) {
|
private suspend fun uploadKeys(request: Request.KeysUpload) {
|
||||||
val response = this.sender.uploadKeys(request)
|
try {
|
||||||
this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
|
val response = requestSender.uploadKeys(request)
|
||||||
|
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO uploadKeys(): error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun queryKeys(request: Request.KeysQuery) {
|
private suspend fun queryKeys(request: Request.KeysQuery) {
|
||||||
try {
|
try {
|
||||||
val response = this.sender.queryKeys(request)
|
val response = requestSender.queryKeys(request)
|
||||||
this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
|
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response)
|
||||||
|
|
||||||
// Update the shields!
|
// Update the shields!
|
||||||
cryptoCoroutineScope.launch {
|
cryptoCoroutineScope.launch {
|
||||||
@ -798,69 +697,44 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
|
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO doKeyDownloadForUsers(): error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendToDevice(request: Request.ToDevice) {
|
private suspend fun sendToDevice(request: Request.ToDevice) {
|
||||||
this.sender.sendToDevice(request)
|
try {
|
||||||
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
|
requestSender.sendToDevice(request)
|
||||||
|
olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}")
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendToDevice(): error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun claimKeys(request: Request.KeysClaim) {
|
private suspend fun claimKeys(request: Request.KeysClaim) {
|
||||||
val response = this.sender.claimKeys(request)
|
try {
|
||||||
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
|
val response = requestSender.claimKeys(request)
|
||||||
|
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO claimKeys(): error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun signatureUpload(request: Request.SignatureUpload) {
|
private suspend fun signatureUpload(request: Request.SignatureUpload) {
|
||||||
this.sender.sendSignatureUpload(request)
|
try {
|
||||||
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}")
|
val response = requestSender.sendSignatureUpload(request)
|
||||||
|
olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, response)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO signatureUpload(): error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendOutgoingRequests() {
|
private suspend fun sendRoomMessage(request: Request.RoomMessage) {
|
||||||
outgoingRequestsLock.withLock {
|
try {
|
||||||
coroutineScope {
|
Timber.v("SendRoomMessage: $request")
|
||||||
olmMachine.outgoingRequests().map {
|
val response = requestSender.sendRoomMessage(request)
|
||||||
when (it) {
|
olmMachine.markRequestAsSent(request.requestId, RequestType.ROOM_MESSAGE, response)
|
||||||
is Request.KeysUpload -> {
|
} catch (throwable: Throwable) {
|
||||||
async {
|
Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendRoomMessage(): error")
|
||||||
uploadKeys(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Request.KeysQuery -> {
|
|
||||||
async {
|
|
||||||
queryKeys(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Request.ToDevice -> {
|
|
||||||
async {
|
|
||||||
sendToDevice(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Request.KeysClaim -> {
|
|
||||||
async {
|
|
||||||
claimKeys(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Request.RoomMessage -> {
|
|
||||||
async {
|
|
||||||
sender.sendRoomMessage(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Request.SignatureUpload -> {
|
|
||||||
async {
|
|
||||||
signatureUpload(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Request.KeysBackup -> {
|
|
||||||
async {
|
|
||||||
// The rust-sdk won't ever produce KeysBackup requests here,
|
|
||||||
// those only get explicitly created.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.joinAll()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -991,18 +865,17 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
val cancellation = requestPair.cancellation
|
val cancellation = requestPair.cancellation
|
||||||
val request = requestPair.keyRequest
|
val request = requestPair.keyRequest
|
||||||
|
|
||||||
if (cancellation != null) {
|
when (cancellation) {
|
||||||
when (cancellation) {
|
is Request.ToDevice -> {
|
||||||
is Request.ToDevice -> {
|
sendToDevice(cancellation)
|
||||||
sendToDevice(cancellation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
when (request) {
|
when (request) {
|
||||||
is Request.ToDevice -> {
|
is Request.ToDevice -> {
|
||||||
sendToDevice(request)
|
sendToDevice(request)
|
||||||
}
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1082,18 +955,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
cryptoStore.logDbUsageInfo()
|
cryptoStore.logDbUsageInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
|
override suspend fun prepareToEncrypt(roomId: String) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
||||||
// Ensure to load all room members
|
// Ensure to load all room members
|
||||||
try {
|
try {
|
||||||
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
|
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
|
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
|
||||||
callback.onFailure(failure)
|
throw failure
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
|
|
||||||
val algorithm = getEncryptionAlgorithm(roomId)
|
val algorithm = getEncryptionAlgorithm(roomId)
|
||||||
@ -1101,19 +972,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
if (algorithm == null) {
|
if (algorithm == null) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
||||||
callback.onFailure(IllegalArgumentException("Missing algorithm"))
|
throw IllegalArgumentException("Missing algorithm")
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
runCatching {
|
|
||||||
preshareRoomKey(roomId, userIds)
|
preshareRoomKey(roomId, userIds)
|
||||||
}.fold(
|
} catch (failure: Throwable) {
|
||||||
{ callback.onSuccess(Unit) },
|
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to PreshareRoomKey")
|
||||||
{
|
}
|
||||||
Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
|
|
||||||
callback.onFailure(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,13 +16,14 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||||
import uniffi.olm.CryptoStoreException
|
import uniffi.olm.CryptoStoreException
|
||||||
import uniffi.olm.OlmMachine
|
import uniffi.olm.OlmMachine
|
||||||
@ -39,16 +40,17 @@ internal class Device(
|
|||||||
private val machine: OlmMachine,
|
private val machine: OlmMachine,
|
||||||
private var inner: InnerDevice,
|
private var inner: InnerDevice,
|
||||||
private val sender: RequestSender,
|
private val sender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val listeners: ArrayList<VerificationService.Listener>
|
private val listeners: ArrayList<VerificationService.Listener>
|
||||||
) {
|
) {
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
private suspend fun refreshData() {
|
private suspend fun refreshData() {
|
||||||
val device = withContext(Dispatchers.IO) {
|
val device = withContext(coroutineDispatchers.io) {
|
||||||
machine.getDevice(inner.userId, inner.deviceId)
|
machine.getDevice(inner.userId, inner.deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
this.inner = device
|
inner = device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,12 +68,12 @@ internal class Device(
|
|||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
|
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
|
||||||
val stringMethods = prepareMethods(methods)
|
val stringMethods = prepareMethods(methods)
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(coroutineDispatchers.io) {
|
||||||
machine.requestVerificationWithDevice(inner.userId, inner.deviceId, stringMethods)
|
machine.requestVerificationWithDevice(inner.userId, inner.deviceId, stringMethods)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (result != null) {
|
return if (result != null) {
|
||||||
this.sender.sendVerificationRequest(result.request)
|
sender.sendVerificationRequest(result.request)
|
||||||
result.verification
|
result.verification
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@ -89,14 +91,18 @@ internal class Device(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun startVerification(): SasVerification? {
|
suspend fun startVerification(): SasVerification? {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(coroutineDispatchers.io) {
|
||||||
machine.startSasWithDevice(inner.userId, inner.deviceId)
|
machine.startSasWithDevice(inner.userId, inner.deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (result != null) {
|
return if (result != null) {
|
||||||
this.sender.sendVerificationRequest(result.request)
|
sender.sendVerificationRequest(result.request)
|
||||||
SasVerification(
|
SasVerification(
|
||||||
this.machine, result.sas, this.sender, this.listeners,
|
machine = machine,
|
||||||
|
inner = result.sas,
|
||||||
|
sender = sender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = listeners
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@ -111,7 +117,7 @@ internal class Device(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun markAsTrusted() {
|
suspend fun markAsTrusted() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
|
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,11 +133,11 @@ internal class Device(
|
|||||||
*/
|
*/
|
||||||
@Throws(SignatureException::class)
|
@Throws(SignatureException::class)
|
||||||
suspend fun verify(): Boolean {
|
suspend fun verify(): Boolean {
|
||||||
val request = withContext(Dispatchers.IO) {
|
val request = withContext(coroutineDispatchers.io) {
|
||||||
machine.verifyDevice(inner.userId, inner.deviceId)
|
machine.verifyDevice(inner.userId, inner.deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sender.sendSignatureUpload(request)
|
sender.sendSignatureUpload(request)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -151,20 +157,20 @@ internal class Device(
|
|||||||
* This will not fetch out fresh data from the Rust side.
|
* This will not fetch out fresh data from the Rust side.
|
||||||
**/
|
**/
|
||||||
internal fun toCryptoDeviceInfo(): CryptoDeviceInfo {
|
internal fun toCryptoDeviceInfo(): CryptoDeviceInfo {
|
||||||
val keys = this.inner.keys.map { (keyId, key) -> "$keyId:$this.inner.deviceId" to key }.toMap()
|
val keys = inner.keys.map { (keyId, key) -> "$keyId:$inner.deviceId" to key }.toMap()
|
||||||
|
|
||||||
return CryptoDeviceInfo(
|
return CryptoDeviceInfo(
|
||||||
this.inner.deviceId,
|
deviceId = inner.deviceId,
|
||||||
this.inner.userId,
|
userId = inner.userId,
|
||||||
this.inner.algorithms,
|
algorithms = inner.algorithms,
|
||||||
keys,
|
keys = keys,
|
||||||
// The Kotlin side doesn't need to care about signatures,
|
// The Kotlin side doesn't need to care about signatures,
|
||||||
// so we're not filling this out
|
// so we're not filling this out
|
||||||
mapOf(),
|
signatures = mapOf(),
|
||||||
UnsignedDeviceInfo(this.inner.displayName),
|
unsigned = UnsignedDeviceInfo(inner.displayName),
|
||||||
DeviceTrustLevel(crossSigningVerified = this.inner.crossSigningTrusted, locallyVerified = this.inner.locallyTrusted),
|
trustLevel = DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted),
|
||||||
this.inner.isBlocked,
|
isBlocked = inner.isBlocked,
|
||||||
// TODO
|
// TODO
|
||||||
null)
|
firstTimeSeenLocalTs = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,13 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import com.squareup.moshi.Moshi
|
||||||
import androidx.lifecycle.MutableLiveData
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
@ -36,6 +38,7 @@ import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
|||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||||
@ -44,8 +47,8 @@ import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
|
||||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uniffi.olm.BackupKeys
|
import uniffi.olm.BackupKeys
|
||||||
@ -64,7 +67,6 @@ import uniffi.olm.setLogger
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import uniffi.olm.OlmMachine as InnerMachine
|
import uniffi.olm.OlmMachine as InnerMachine
|
||||||
import uniffi.olm.ProgressListener as RustProgressListener
|
import uniffi.olm.ProgressListener as RustProgressListener
|
||||||
import uniffi.olm.UserIdentity as RustUserIdentity
|
import uniffi.olm.UserIdentity as RustUserIdentity
|
||||||
@ -81,138 +83,71 @@ private class CryptoProgressListener(private val listener: ProgressListener?) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LiveDevice(
|
private data class UserIdentityCollector(val userId: String, val collector: SendChannel<Optional<MXCrossSigningInfo>>)
|
||||||
internal var userIds: List<String>,
|
: SendChannel<Optional<MXCrossSigningInfo>> by collector
|
||||||
private var observer: DeviceUpdateObserver
|
private data class DevicesCollector(val userIds: List<String>, val collector: SendChannel<List<CryptoDeviceInfo>>)
|
||||||
) : MutableLiveData<List<CryptoDeviceInfo>>() {
|
: SendChannel<List<CryptoDeviceInfo>> by collector
|
||||||
|
private typealias PrivateKeysCollector = SendChannel<Optional<PrivateKeysInfo>>
|
||||||
|
|
||||||
override fun onActive() {
|
private class FlowCollectors {
|
||||||
observer.addDeviceUpdateListener(this)
|
val userIdentityCollectors = ArrayList<UserIdentityCollector>()
|
||||||
}
|
val privateKeyCollectors = ArrayList<PrivateKeysCollector>()
|
||||||
|
val deviceCollectors = ArrayList<DevicesCollector>()
|
||||||
override fun onInactive() {
|
|
||||||
observer.removeDeviceUpdateListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class LiveUserIdentity(
|
|
||||||
internal var userId: String,
|
|
||||||
private var observer: UserIdentityUpdateObserver
|
|
||||||
) : MutableLiveData<Optional<MXCrossSigningInfo>>() {
|
|
||||||
override fun onActive() {
|
|
||||||
observer.addUserIdentityUpdateListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInactive() {
|
|
||||||
observer.removeUserIdentityUpdateListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class LivePrivateCrossSigningKeys(
|
|
||||||
private var observer: PrivateCrossSigningKeysUpdateObserver,
|
|
||||||
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
|
|
||||||
|
|
||||||
override fun onActive() {
|
|
||||||
observer.addUserIdentityUpdateListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInactive() {
|
|
||||||
observer.removeUserIdentityUpdateListener(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRustLogger() {
|
fun setRustLogger() {
|
||||||
setLogger(CryptoLogger() as Logger)
|
setLogger(CryptoLogger() as Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DeviceUpdateObserver {
|
|
||||||
internal val listeners = ConcurrentHashMap<LiveDevice, List<String>>()
|
|
||||||
|
|
||||||
fun addDeviceUpdateListener(device: LiveDevice) {
|
|
||||||
listeners[device] = device.userIds
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeDeviceUpdateListener(device: LiveDevice) {
|
|
||||||
listeners.remove(device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class UserIdentityUpdateObserver {
|
|
||||||
internal val listeners = ConcurrentHashMap<LiveUserIdentity, String>()
|
|
||||||
|
|
||||||
fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
|
|
||||||
listeners[userIdentity] = userIdentity.userId
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
|
|
||||||
listeners.remove(userIdentity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class PrivateCrossSigningKeysUpdateObserver {
|
|
||||||
internal val listeners = ConcurrentHashMap<LivePrivateCrossSigningKeys, Unit>()
|
|
||||||
|
|
||||||
fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
|
|
||||||
listeners[liveKeys] = Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
|
|
||||||
listeners.remove(liveKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class OlmMachine(
|
internal class OlmMachine(
|
||||||
user_id: String,
|
user_id: String,
|
||||||
device_id: String,
|
device_id: String,
|
||||||
path: File,
|
path: File,
|
||||||
deviceObserver: DeviceUpdateObserver,
|
|
||||||
private val requestSender: RequestSender,
|
private val requestSender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val moshi: Moshi
|
||||||
) {
|
) {
|
||||||
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
|
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString(), null)
|
||||||
private val deviceUpdateObserver = deviceObserver
|
|
||||||
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
|
|
||||||
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
|
|
||||||
internal val verificationListeners = ArrayList<VerificationService.Listener>()
|
internal val verificationListeners = ArrayList<VerificationService.Listener>()
|
||||||
|
private val flowCollectors = FlowCollectors()
|
||||||
|
|
||||||
/** Get our own user ID. */
|
/** Get our own user ID. */
|
||||||
fun userId(): String {
|
fun userId(): String {
|
||||||
return this.inner.userId()
|
return inner.userId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get our own device ID. */
|
/** Get our own device ID. */
|
||||||
fun deviceId(): String {
|
fun deviceId(): String {
|
||||||
return this.inner.deviceId()
|
return inner.deviceId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get our own public identity keys ID. */
|
/** Get our own public identity keys ID. */
|
||||||
fun identityKeys(): Map<String, String> {
|
fun identityKeys(): Map<String, String> {
|
||||||
return this.inner.identityKeys()
|
return inner.identityKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inner(): InnerMachine {
|
fun inner(): InnerMachine {
|
||||||
return this.inner
|
return inner
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update all of our live device listeners. */
|
|
||||||
private suspend fun updateLiveDevices() {
|
private suspend fun updateLiveDevices() {
|
||||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
for (deviceCollector in flowCollectors.deviceCollectors) {
|
||||||
val devices = getCryptoDeviceInfo(users)
|
val devices = getCryptoDeviceInfo(deviceCollector.userIds)
|
||||||
liveDevice.postValue(devices)
|
deviceCollector.trySend(devices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateLiveUserIdentities() {
|
private suspend fun updateLiveUserIdentities() {
|
||||||
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) {
|
for (userIdentityCollector in flowCollectors.userIdentityCollectors) {
|
||||||
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
val identity = getIdentity(userIdentityCollector.userId)?.toMxCrossSigningInfo()
|
||||||
liveIdentity.postValue(identity)
|
userIdentityCollector.trySend(identity.toOptional())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateLivePrivateKeys() {
|
private suspend fun updateLivePrivateKeys() {
|
||||||
val keys = this.exportCrossSigningKeys().toOptional()
|
val keys = exportCrossSigningKeys().toOptional()
|
||||||
|
for (privateKeyCollector in flowCollectors.privateKeyCollectors) {
|
||||||
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) {
|
privateKeyCollector.trySend(keys)
|
||||||
liveKeys.postValue(keys)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,18 +155,18 @@ internal class OlmMachine(
|
|||||||
* Get our own device info as [CryptoDeviceInfo].
|
* Get our own device info as [CryptoDeviceInfo].
|
||||||
*/
|
*/
|
||||||
suspend fun ownDevice(): CryptoDeviceInfo {
|
suspend fun ownDevice(): CryptoDeviceInfo {
|
||||||
val deviceId = this.deviceId()
|
val deviceId = deviceId()
|
||||||
|
|
||||||
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
val keys = identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
||||||
|
|
||||||
val crossSigningVerified = when (val ownIdentity = this.getIdentity(this.userId())) {
|
val crossSigningVerified = when (val ownIdentity = getIdentity(userId())) {
|
||||||
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
|
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
return CryptoDeviceInfo(
|
return CryptoDeviceInfo(
|
||||||
this.deviceId(),
|
deviceId(),
|
||||||
this.userId(),
|
userId(),
|
||||||
// TODO pass the algorithms here.
|
// TODO pass the algorithms here.
|
||||||
listOf(),
|
listOf(),
|
||||||
keys,
|
keys,
|
||||||
@ -251,7 +186,7 @@ internal class OlmMachine(
|
|||||||
* @return the list of requests that needs to be sent to the homeserver
|
* @return the list of requests that needs to be sent to the homeserver
|
||||||
*/
|
*/
|
||||||
suspend fun outgoingRequests(): List<Request> =
|
suspend fun outgoingRequests(): List<Request> =
|
||||||
withContext(Dispatchers.IO) { inner.outgoingRequests() }
|
withContext(coroutineDispatchers.io) { inner.outgoingRequests() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a request that was sent to the server as sent.
|
* Mark a request that was sent to the server as sent.
|
||||||
@ -268,9 +203,8 @@ internal class OlmMachine(
|
|||||||
requestType: RequestType,
|
requestType: RequestType,
|
||||||
responseBody: String
|
responseBody: String
|
||||||
) =
|
) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
inner.markRequestAsSent(requestId, requestType, responseBody)
|
inner.markRequestAsSent(requestId, requestType, responseBody)
|
||||||
|
|
||||||
if (requestType == RequestType.KEYS_QUERY) {
|
if (requestType == RequestType.KEYS_QUERY) {
|
||||||
updateLiveDevices()
|
updateLiveDevices()
|
||||||
updateLiveUserIdentities()
|
updateLiveUserIdentities()
|
||||||
@ -296,7 +230,7 @@ internal class OlmMachine(
|
|||||||
deviceChanges: DeviceListResponse?,
|
deviceChanges: DeviceListResponse?,
|
||||||
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
||||||
): ToDeviceSyncResponse {
|
): ToDeviceSyncResponse {
|
||||||
val response = withContext(Dispatchers.IO) {
|
val response = withContext(coroutineDispatchers.io) {
|
||||||
val counts: MutableMap<String, Int> = mutableMapOf()
|
val counts: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
|
||||||
if (keyCounts?.signedCurve25519 != null) {
|
if (keyCounts?.signedCurve25519 != null) {
|
||||||
@ -306,7 +240,7 @@ internal class OlmMachine(
|
|||||||
val devices =
|
val devices =
|
||||||
DeviceLists(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
|
DeviceLists(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
|
||||||
val adapter =
|
val adapter =
|
||||||
MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
|
moshi.adapter(ToDeviceSyncResponse::class.java)
|
||||||
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
|
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
|
||||||
|
|
||||||
// TODO once our sync response type parses the unused fallback key
|
// TODO once our sync response type parses the unused fallback key
|
||||||
@ -315,11 +249,17 @@ internal class OlmMachine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We may get cross signing keys over a to-device event, update our listeners.
|
// We may get cross signing keys over a to-device event, update our listeners.
|
||||||
this.updateLivePrivateKeys()
|
updateLivePrivateKeys()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) {
|
||||||
|
val adapter = moshi.adapter(Event::class.java)
|
||||||
|
val serializedEvent = adapter.toJson(event)
|
||||||
|
inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the given list of users to be tracked, triggering a key query request for them.
|
* Mark the given list of users to be tracked, triggering a key query request for them.
|
||||||
*
|
*
|
||||||
@ -329,7 +269,7 @@ internal class OlmMachine(
|
|||||||
* @param users The users that should be queued up for a key query.
|
* @param users The users that should be queued up for a key query.
|
||||||
*/
|
*/
|
||||||
suspend fun updateTrackedUsers(users: List<String>) =
|
suspend fun updateTrackedUsers(users: List<String>) =
|
||||||
withContext(Dispatchers.IO) { inner.updateTrackedUsers(users) }
|
withContext(coroutineDispatchers.io) { inner.updateTrackedUsers(users) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given user is considered to be tracked.
|
* Check if the given user is considered to be tracked.
|
||||||
@ -338,7 +278,7 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
fun isUserTracked(userId: String): Boolean {
|
fun isUserTracked(userId: String): Boolean {
|
||||||
return this.inner.isUserTracked(userId)
|
return inner.isUserTracked(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -355,7 +295,7 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun getMissingSessions(users: List<String>): Request? =
|
suspend fun getMissingSessions(users: List<String>): Request? =
|
||||||
withContext(Dispatchers.IO) { inner.getMissingSessions(users) }
|
withContext(coroutineDispatchers.io) { inner.getMissingSessions(users) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share a room key with the given list of users for the given room.
|
* Share a room key with the given list of users for the given room.
|
||||||
@ -377,7 +317,7 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
|
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
|
||||||
withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) }
|
withContext(coroutineDispatchers.io) { inner.shareRoomKey(roomId, users) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt the given event with the given type and content for the given room.
|
* Encrypt the given event with the given type and content for the given room.
|
||||||
@ -411,8 +351,8 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
|
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
val adapter = moshi.adapter<Content>(Map::class.java)
|
||||||
val contentString = adapter.toJson(content)
|
val contentString = adapter.toJson(content)
|
||||||
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
||||||
adapter.fromJson(encrypted)!!
|
adapter.fromJson(encrypted)!!
|
||||||
@ -429,17 +369,17 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
val adapter = moshi.adapter(Event::class.java)
|
||||||
val serializedEvent = adapter.toJson(event)
|
|
||||||
try {
|
try {
|
||||||
if (event.roomId.isNullOrBlank()) {
|
if (event.roomId.isNullOrBlank()) {
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
}
|
}
|
||||||
|
val serializedEvent = adapter.toJson(event)
|
||||||
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId)
|
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId)
|
||||||
|
|
||||||
val deserializationAdapter =
|
val deserializationAdapter =
|
||||||
MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java)
|
moshi.adapter<JsonDict>(Map::class.java)
|
||||||
val clearEvent = deserializationAdapter.fromJson(decrypted.clearEvent)
|
val clearEvent = deserializationAdapter.fromJson(decrypted.clearEvent)
|
||||||
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
|
||||||
@ -469,8 +409,8 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(DecryptionException::class)
|
@Throws(DecryptionException::class)
|
||||||
suspend fun requestRoomKey(event: Event): KeyRequestPair =
|
suspend fun requestRoomKey(event: Event): KeyRequestPair =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
val adapter = moshi.adapter(Event::class.java)
|
||||||
val serializedEvent = adapter.toJson(event)
|
val serializedEvent = adapter.toJson(event)
|
||||||
|
|
||||||
inner.requestRoomKey(serializedEvent, event.roomId!!)
|
inner.requestRoomKey(serializedEvent, event.roomId!!)
|
||||||
@ -488,7 +428,7 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray =
|
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray =
|
||||||
withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() }
|
withContext(coroutineDispatchers.io) { inner.exportKeys(passphrase, rounds).toByteArray() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import room keys from the given serialized key export.
|
* Import room keys from the given serialized key export.
|
||||||
@ -505,7 +445,7 @@ internal class OlmMachine(
|
|||||||
passphrase: String,
|
passphrase: String,
|
||||||
listener: ProgressListener?
|
listener: ProgressListener?
|
||||||
): ImportRoomKeysResult =
|
): ImportRoomKeysResult =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val decodedKeys = String(keys, Charset.defaultCharset())
|
val decodedKeys = String(keys, Charset.defaultCharset())
|
||||||
|
|
||||||
val rustListener = CryptoProgressListener(listener)
|
val rustListener = CryptoProgressListener(listener)
|
||||||
@ -520,8 +460,8 @@ internal class OlmMachine(
|
|||||||
keys: List<MegolmSessionData>,
|
keys: List<MegolmSessionData>,
|
||||||
listener: ProgressListener?
|
listener: ProgressListener?
|
||||||
): ImportRoomKeysResult =
|
): ImportRoomKeysResult =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(List::class.java)
|
val adapter = moshi.adapter(List::class.java)
|
||||||
|
|
||||||
// If the key backup is too big we take the risk of causing OOM
|
// If the key backup is too big we take the risk of causing OOM
|
||||||
// when serializing to json
|
// when serializing to json
|
||||||
@ -550,25 +490,31 @@ internal class OlmMachine(
|
|||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun getIdentity(userId: String): UserIdentities? {
|
suspend fun getIdentity(userId: String): UserIdentities? {
|
||||||
val identity = withContext(Dispatchers.IO) {
|
val identity = withContext(coroutineDispatchers.io) {
|
||||||
inner.getIdentity(userId)
|
inner.getIdentity(userId)
|
||||||
}
|
}
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
|
val adapter = moshi.adapter(RestKeyInfo::class.java)
|
||||||
|
|
||||||
return when (identity) {
|
return when (identity) {
|
||||||
is RustUserIdentity.Other -> {
|
is RustUserIdentity.Other -> {
|
||||||
val verified = this.inner().isIdentityVerified(userId)
|
val verified = inner().isIdentityVerified(userId)
|
||||||
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
|
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
|
||||||
trustLevel = DeviceTrustLevel(verified, verified)
|
trustLevel = DeviceTrustLevel(verified, verified)
|
||||||
}
|
}
|
||||||
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel().apply {
|
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel().apply {
|
||||||
trustLevel = DeviceTrustLevel(verified, verified)
|
trustLevel = DeviceTrustLevel(verified, verified)
|
||||||
}
|
}
|
||||||
|
UserIdentity(
|
||||||
UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender)
|
userId = identity.userId,
|
||||||
|
masterKey = masterKey,
|
||||||
|
selfSigningKey = selfSigningKey,
|
||||||
|
olmMachine = this,
|
||||||
|
requestSender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is RustUserIdentity.Own -> {
|
is RustUserIdentity.Own -> {
|
||||||
val verified = this.inner().isIdentityVerified(userId)
|
val verified = inner().isIdentityVerified(userId)
|
||||||
|
|
||||||
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
|
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply {
|
||||||
trustLevel = DeviceTrustLevel(verified, verified)
|
trustLevel = DeviceTrustLevel(verified, verified)
|
||||||
@ -579,13 +525,14 @@ internal class OlmMachine(
|
|||||||
val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel()
|
val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel()
|
||||||
|
|
||||||
OwnUserIdentity(
|
OwnUserIdentity(
|
||||||
identity.userId,
|
userId = identity.userId,
|
||||||
masterKey,
|
masterKey = masterKey,
|
||||||
selfSigningKey,
|
selfSigningKey = selfSigningKey,
|
||||||
userSigningKey,
|
userSigningKey = userSigningKey,
|
||||||
identity.trustsOurOwnDevice,
|
trustsOurOwnDevice = identity.trustsOurOwnDevice,
|
||||||
this,
|
olmMachine = this,
|
||||||
this.requestSender
|
requestSender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
null -> null
|
null -> null
|
||||||
@ -605,27 +552,35 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? {
|
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? {
|
||||||
return if (userId == userId() && deviceId == deviceId()) {
|
return getDevice(userId, deviceId)?.toCryptoDeviceInfo()
|
||||||
// Our own device isn't part of our store on the Rust side, return it
|
|
||||||
// using our ownDevice method
|
|
||||||
ownDevice()
|
|
||||||
} else {
|
|
||||||
getDevice(userId, deviceId)?.toCryptoDeviceInfo()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun getDevice(userId: String, deviceId: String): Device? {
|
suspend fun getDevice(userId: String, deviceId: String): Device? {
|
||||||
val device = withContext(Dispatchers.IO) {
|
val device = withContext(coroutineDispatchers.io) {
|
||||||
inner.getDevice(userId, deviceId)
|
inner.getDevice(userId, deviceId)
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
return Device(this.inner, device, this.requestSender, this.verificationListeners)
|
return Device(
|
||||||
|
machine = inner,
|
||||||
|
inner = device,
|
||||||
|
sender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserDevices(userId: String): List<Device> {
|
suspend fun getUserDevices(userId: String): List<Device> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(coroutineDispatchers.io) {
|
||||||
inner.getUserDevices(userId).map { Device(inner, it, requestSender, verificationListeners) }
|
inner.getUserDevices(userId).map {
|
||||||
|
Device(
|
||||||
|
machine = inner,
|
||||||
|
inner = it,
|
||||||
|
sender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,15 +593,17 @@ internal class OlmMachine(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
|
return getUserDevices(userId).map { it.toCryptoDeviceInfo() }
|
||||||
|
/*
|
||||||
// EA doesn't differentiate much between our own and other devices of
|
// EA doesn't differentiate much between our own and other devices of
|
||||||
// while the rust-sdk does, append our own device here.
|
// while the rust-sdk does, append our own device here.
|
||||||
if (userId == this.userId()) {
|
if (userId == userId()) {
|
||||||
devices.add(this.ownDevice())
|
devices.add(ownDevice())
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -660,7 +617,7 @@ internal class OlmMachine(
|
|||||||
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
||||||
|
|
||||||
for (user in userIds) {
|
for (user in userIds) {
|
||||||
val devices = this.getCryptoDeviceInfo(user)
|
val devices = getCryptoDeviceInfo(user)
|
||||||
plainDevices.addAll(devices)
|
plainDevices.addAll(devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -669,7 +626,7 @@ internal class OlmMachine(
|
|||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
suspend fun forceKeyDownload(userIds: List<String>) {
|
suspend fun forceKeyDownload(userIds: List<String>) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val requestId = UUID.randomUUID().toString()
|
val requestId = UUID.randomUUID().toString()
|
||||||
val response = requestSender.queryKeys(Request.KeysQuery(requestId, userIds))
|
val response = requestSender.queryKeys(Request.KeysQuery(requestId, userIds))
|
||||||
markRequestAsSent(requestId, RequestType.KEYS_QUERY, response)
|
markRequestAsSent(requestId, RequestType.KEYS_QUERY, response)
|
||||||
@ -680,7 +637,7 @@ internal class OlmMachine(
|
|||||||
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
|
|
||||||
for (user in userIds) {
|
for (user in userIds) {
|
||||||
val devices = this.getCryptoDeviceInfo(user)
|
val devices = getCryptoDeviceInfo(user)
|
||||||
|
|
||||||
for (device in devices) {
|
for (device in devices) {
|
||||||
userMap.setObject(user, device.deviceId, device)
|
userMap.setObject(user, device.deviceId, device)
|
||||||
@ -697,7 +654,18 @@ internal class OlmMachine(
|
|||||||
* The key query request will be retried a few time in case of shaky connection, but could fail.
|
* The key query request will be retried a few time in case of shaky connection, but could fail.
|
||||||
*/
|
*/
|
||||||
suspend fun ensureUserDevicesMap(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo> {
|
suspend fun ensureUserDevicesMap(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
val toDownload = if (forceDownload) {
|
ensureUsersKeys(userIds, forceDownload)
|
||||||
|
return getUserDevicesMap(userIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user is untracked or forceDownload is set to true, a key query request will be made.
|
||||||
|
* It will suspend until query response.
|
||||||
|
*
|
||||||
|
* The key query request will be retried a few time in case of shaky connection, but could fail.
|
||||||
|
*/
|
||||||
|
suspend fun ensureUsersKeys(userIds: List<String>, forceDownload: Boolean = false) {
|
||||||
|
val userIdsToFetchKeys = if (forceDownload) {
|
||||||
userIds
|
userIds
|
||||||
} else {
|
} else {
|
||||||
userIds.mapNotNull { userId ->
|
userIds.mapNotNull { userId ->
|
||||||
@ -706,26 +674,34 @@ internal class OlmMachine(
|
|||||||
updateTrackedUsers(it)
|
updateTrackedUsers(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tryOrNull("Failed to download keys for $toDownload") {
|
tryOrNull("Failed to download keys for $userIdsToFetchKeys") {
|
||||||
forceKeyDownload(toDownload)
|
forceKeyDownload(userIdsToFetchKeys)
|
||||||
}
|
}
|
||||||
return getUserDevicesMap(userIds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
fun getLiveUserIdentity(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
||||||
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
return channelFlow {
|
||||||
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver)
|
val userIdentityCollector = UserIdentityCollector(userId, this)
|
||||||
liveIdentity.value = identity
|
val onClose = safeInvokeOnClose {
|
||||||
|
flowCollectors.userIdentityCollectors.remove(userIdentityCollector)
|
||||||
return liveIdentity
|
}
|
||||||
|
flowCollectors.userIdentityCollectors.add(userIdentityCollector)
|
||||||
|
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
||||||
|
send(identity)
|
||||||
|
onClose.await()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
fun getLivePrivateCrossSigningKeys(): Flow<Optional<PrivateKeysInfo>> {
|
||||||
val keys = this.exportCrossSigningKeys().toOptional()
|
return channelFlow {
|
||||||
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver)
|
val onClose = safeInvokeOnClose {
|
||||||
liveKeys.value = keys
|
flowCollectors.privateKeyCollectors.remove(this)
|
||||||
|
}
|
||||||
return liveKeys
|
flowCollectors.privateKeyCollectors.add(this)
|
||||||
|
val keys = this@OlmMachine.exportCrossSigningKeys().toOptional()
|
||||||
|
send(keys)
|
||||||
|
onClose.await()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -736,14 +712,19 @@ internal class OlmMachine(
|
|||||||
*
|
*
|
||||||
* @param userIds The ids of the device owners.
|
* @param userIds The ids of the device owners.
|
||||||
*
|
*
|
||||||
* @return The list of Devices or an empty list if there aren't any.
|
* @return The list of Devices or an empty list if there aren't any as a Flow.
|
||||||
*/
|
*/
|
||||||
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
fun getLiveDevices(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
|
||||||
val plainDevices = getCryptoDeviceInfo(userIds)
|
return channelFlow {
|
||||||
val devices = LiveDevice(userIds, deviceUpdateObserver)
|
val devicesCollector = DevicesCollector(userIds, this)
|
||||||
devices.value = plainDevices
|
val onClose = safeInvokeOnClose {
|
||||||
|
flowCollectors.deviceCollectors.remove(devicesCollector)
|
||||||
return devices
|
}
|
||||||
|
flowCollectors.deviceCollectors.add(devicesCollector)
|
||||||
|
val devices = getCryptoDeviceInfo(userIds)
|
||||||
|
send(devices)
|
||||||
|
onClose.await()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Discard the currently active room key for the given room if there is one. */
|
/** Discard the currently active room key for the given room if there is one. */
|
||||||
@ -760,26 +741,27 @@ internal class OlmMachine(
|
|||||||
* @return The list of [VerificationRequest] that we share with the given user
|
* @return The list of [VerificationRequest] that we share with the given user
|
||||||
*/
|
*/
|
||||||
fun getVerificationRequests(userId: String): List<VerificationRequest> {
|
fun getVerificationRequests(userId: String): List<VerificationRequest> {
|
||||||
return this.inner.getVerificationRequests(userId).map {
|
return inner.getVerificationRequests(userId).map {
|
||||||
VerificationRequest(
|
VerificationRequest(
|
||||||
this.inner,
|
machine = inner,
|
||||||
it,
|
inner = it,
|
||||||
this.requestSender,
|
sender = requestSender,
|
||||||
this.verificationListeners,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a verification request for the given user with the given flow ID */
|
/** Get a verification request for the given user with the given flow ID */
|
||||||
fun getVerificationRequest(userId: String, flowId: String): VerificationRequest? {
|
fun getVerificationRequest(userId: String, flowId: String): VerificationRequest? {
|
||||||
val request = this.inner.getVerificationRequest(userId, flowId)
|
val request = inner.getVerificationRequest(userId, flowId)
|
||||||
|
|
||||||
return if (request != null) {
|
return if (request != null) {
|
||||||
VerificationRequest(
|
VerificationRequest(
|
||||||
this.inner,
|
machine = inner,
|
||||||
request,
|
inner = request,
|
||||||
requestSender,
|
sender = requestSender,
|
||||||
this.verificationListeners,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@ -792,13 +774,26 @@ internal class OlmMachine(
|
|||||||
* verification.
|
* verification.
|
||||||
*/
|
*/
|
||||||
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
|
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
|
||||||
return when (val verification = this.inner.getVerification(userId, flowId)) {
|
return when (val verification = inner.getVerification(userId, flowId)) {
|
||||||
is uniffi.olm.Verification.QrCodeV1 -> {
|
is uniffi.olm.Verification.QrCodeV1 -> {
|
||||||
val request = this.getVerificationRequest(userId, flowId) ?: return null
|
val request = getVerificationRequest(userId, flowId) ?: return null
|
||||||
QrCodeVerification(inner, request, verification.qrcode, requestSender, verificationListeners)
|
QrCodeVerification(
|
||||||
|
machine = inner,
|
||||||
|
request = request,
|
||||||
|
inner = verification.qrcode,
|
||||||
|
sender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is uniffi.olm.Verification.SasV1 -> {
|
is uniffi.olm.Verification.SasV1 -> {
|
||||||
SasVerification(inner, verification.sas, requestSender, verificationListeners)
|
SasVerification(
|
||||||
|
machine = inner,
|
||||||
|
inner = verification.sas,
|
||||||
|
sender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners
|
||||||
|
)
|
||||||
}
|
}
|
||||||
null -> {
|
null -> {
|
||||||
// This branch exists because scanning a QR code is tied to the QrCodeVerification,
|
// This branch exists because scanning a QR code is tied to the QrCodeVerification,
|
||||||
@ -808,7 +803,14 @@ internal class OlmMachine(
|
|||||||
val request = getVerificationRequest(userId, flowId) ?: return null
|
val request = getVerificationRequest(userId, flowId) ?: return null
|
||||||
|
|
||||||
if (request.canScanQrCodes()) {
|
if (request.canScanQrCodes()) {
|
||||||
QrCodeVerification(inner, request, null, requestSender, verificationListeners)
|
QrCodeVerification(
|
||||||
|
machine = inner,
|
||||||
|
request = request,
|
||||||
|
inner = null,
|
||||||
|
sender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = verificationListeners
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -817,23 +819,22 @@ internal class OlmMachine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||||
val requests = withContext(Dispatchers.IO) {
|
val requests = withContext(coroutineDispatchers.io) {
|
||||||
inner.bootstrapCrossSigning()
|
inner.bootstrapCrossSigning()
|
||||||
}
|
}
|
||||||
|
requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
|
||||||
this.requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
|
requestSender.sendSignatureUpload(requests.signatureRequest)
|
||||||
this.requestSender.sendSignatureUpload(requests.signatureRequest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the status of our private cross signing keys, i.e. which private keys do we have stored locally.
|
* Get the status of our private cross signing keys, i.e. which private keys do we have stored locally.
|
||||||
*/
|
*/
|
||||||
fun crossSigningStatus(): CrossSigningStatus {
|
fun crossSigningStatus(): CrossSigningStatus {
|
||||||
return this.inner.crossSigningStatus()
|
return inner.crossSigningStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun exportCrossSigningKeys(): PrivateKeysInfo? {
|
suspend fun exportCrossSigningKeys(): PrivateKeysInfo? {
|
||||||
val export = withContext(Dispatchers.IO) {
|
val export = withContext(coroutineDispatchers.io) {
|
||||||
inner.exportCrossSigningKeys()
|
inner.exportCrossSigningKeys()
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
@ -844,7 +845,7 @@ internal class OlmMachine(
|
|||||||
val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user)
|
val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user)
|
||||||
|
|
||||||
var result: UserTrustResult
|
var result: UserTrustResult
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
result = try {
|
result = try {
|
||||||
inner.importCrossSigningKeys(rustExport)
|
inner.importCrossSigningKeys(rustExport)
|
||||||
|
|
||||||
@ -861,21 +862,21 @@ internal class OlmMachine(
|
|||||||
UserTrustResult.Failure(failure.localizedMessage)
|
UserTrustResult.Failure(failure.localizedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(coroutineDispatchers.main) {
|
||||||
this@OlmMachine.updateLivePrivateKeys()
|
this@OlmMachine.updateLivePrivateKeys()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sign(message: String): Map<String, Map<String, String>> {
|
suspend fun sign(message: String): Map<String, Map<String, String>> {
|
||||||
return withContext(Dispatchers.Default) {
|
return withContext(coroutineDispatchers.computation) {
|
||||||
inner.sign(message)
|
inner.sign(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun enableBackupV1(key: String, version: String) {
|
suspend fun enableBackupV1(key: String, version: String) {
|
||||||
return withContext(Dispatchers.Default) {
|
return withContext(coroutineDispatchers.computation) {
|
||||||
val backupKey = MegolmV1BackupKey(key, mapOf(), null, MXCRYPTO_ALGORITHM_MEGOLM_BACKUP)
|
val backupKey = MegolmV1BackupKey(key, mapOf(), null, MXCRYPTO_ALGORITHM_MEGOLM_BACKUP)
|
||||||
inner.enableBackupV1(backupKey, version)
|
inner.enableBackupV1(backupKey, version)
|
||||||
}
|
}
|
||||||
@ -891,26 +892,29 @@ internal class OlmMachine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
fun roomKeyCounts(): RoomKeyCounts {
|
suspend fun roomKeyCounts(): RoomKeyCounts {
|
||||||
// TODO convert this to a suspendable method
|
return withContext(coroutineDispatchers.computation) {
|
||||||
return inner.roomKeyCounts()
|
inner.roomKeyCounts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
fun getBackupKeys(): BackupKeys? {
|
suspend fun getBackupKeys(): BackupKeys? {
|
||||||
// TODO this needs to be suspendable
|
return withContext(coroutineDispatchers.computation) {
|
||||||
return inner.getBackupKeys()
|
inner.getBackupKeys()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
fun saveRecoveryKey(key: String?, version: String?) {
|
suspend fun saveRecoveryKey(key: String?, version: String?) {
|
||||||
// TODO convert this to a suspendable method
|
withContext(coroutineDispatchers.computation) {
|
||||||
inner.saveRecoveryKey(key, version)
|
inner.saveRecoveryKey(key, version)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun backupRoomKeys(): Request? {
|
suspend fun backupRoomKeys(): Request? {
|
||||||
return withContext(Dispatchers.Default) {
|
return withContext(coroutineDispatchers.computation) {
|
||||||
Timber.d("BACKUP CREATING REQUEST")
|
Timber.d("BACKUP CREATING REQUEST")
|
||||||
val request = inner.backupRoomKeys()
|
val request = inner.backupRoomKeys()
|
||||||
Timber.d("BACKUP CREATED REQUEST: $request")
|
Timber.d("BACKUP CREATED REQUEST: $request")
|
||||||
@ -920,9 +924,8 @@ internal class OlmMachine(
|
|||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun checkAuthDataSignature(authData: MegolmBackupAuthData): Boolean {
|
suspend fun checkAuthDataSignature(authData: MegolmBackupAuthData): Boolean {
|
||||||
return withContext(Dispatchers.Default) {
|
return withContext(coroutineDispatchers.computation) {
|
||||||
val adapter = MoshiProvider
|
val adapter = moshi
|
||||||
.providesMoshi()
|
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||||
.build()
|
.build()
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
@ -28,10 +31,16 @@ internal class OlmMachineProvider @Inject constructor(
|
|||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@DeviceId private val deviceId: String?,
|
@DeviceId private val deviceId: String?,
|
||||||
@SessionFilesDirectory private val dataDir: File,
|
@SessionFilesDirectory private val dataDir: File,
|
||||||
requestSender: RequestSender
|
requestSender: RequestSender,
|
||||||
|
coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
moshi: Moshi
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
var olmMachine: OlmMachine = OlmMachine(
|
||||||
|
user_id = userId,
|
||||||
var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, requestSender)
|
device_id = deviceId!!,
|
||||||
|
path = dataDir,
|
||||||
|
requestSender = requestSender,
|
||||||
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
moshi = moshi)
|
||||||
}
|
}
|
||||||
|
@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
||||||
import uniffi.olm.CryptoStoreException
|
import uniffi.olm.CryptoStoreException
|
||||||
import uniffi.olm.OlmMachine
|
import uniffi.olm.OlmMachine
|
||||||
@ -37,13 +37,15 @@ internal class QrCodeVerification(
|
|||||||
private var request: VerificationRequest,
|
private var request: VerificationRequest,
|
||||||
private var inner: QrCode?,
|
private var inner: QrCode?,
|
||||||
private val sender: RequestSender,
|
private val sender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
listeners: ArrayList<VerificationService.Listener>
|
listeners: ArrayList<VerificationService.Listener>
|
||||||
) : QrCodeVerificationTransaction {
|
) : QrCodeVerificationTransaction {
|
||||||
|
|
||||||
private val dispatcher = UpdateDispatcher(listeners)
|
private val dispatcher = UpdateDispatcher(listeners)
|
||||||
|
|
||||||
private fun dispatchTxUpdated() {
|
private fun dispatchTxUpdated() {
|
||||||
refreshData()
|
refreshData()
|
||||||
this.dispatcher.dispatchTxUpdated(this)
|
dispatcher.dispatchTxUpdated(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generate, if possible, data that should be encoded as a QR code for QR code verification.
|
/** Generate, if possible, data that should be encoded as a QR code for QR code verification.
|
||||||
@ -60,27 +62,25 @@ internal class QrCodeVerification(
|
|||||||
*/
|
*/
|
||||||
override val qrCodeText: String?
|
override val qrCodeText: String?
|
||||||
get() {
|
get() {
|
||||||
val data = this.inner?.let { this.machine.generateQrCode(it.otherUserId, it.flowId) }
|
val data = inner?.let { machine.generateQrCode(it.otherUserId, it.flowId) }
|
||||||
|
|
||||||
// TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64?
|
// TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64?
|
||||||
return data?.fromBase64()?.toString(Charsets.ISO_8859_1)
|
return data?.fromBase64()?.toString(Charsets.ISO_8859_1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pass the data from a scanned QR code into the QR code verification object */
|
/** Pass the data from a scanned QR code into the QR code verification object */
|
||||||
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
|
override suspend fun userHasScannedOtherQrCode(otherQrCodeText: String) {
|
||||||
runBlocking {
|
request.scanQrCode(otherQrCodeText)
|
||||||
request.scanQrCode(otherQrCodeText)
|
|
||||||
}
|
|
||||||
dispatchTxUpdated()
|
dispatchTxUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Confirm that the other side has indeed scanned the QR code we presented */
|
/** Confirm that the other side has indeed scanned the QR code we presented */
|
||||||
override fun otherUserScannedMyQrCode() {
|
override suspend fun otherUserScannedMyQrCode() {
|
||||||
runBlocking { confirm() }
|
confirm()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cancel the QR code verification, denying that the other side has scanned the QR code */
|
/** Cancel the QR code verification, denying that the other side has scanned the QR code */
|
||||||
override fun otherUserDidNotScannedMyQrCode() {
|
override suspend fun otherUserDidNotScannedMyQrCode() {
|
||||||
// TODO Is this code correct here? The old code seems to do this
|
// TODO Is this code correct here? The old code seems to do this
|
||||||
cancelHelper(CancelCode.MismatchedKeys)
|
cancelHelper(CancelCode.MismatchedKeys)
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ internal class QrCodeVerification(
|
|||||||
override var state: VerificationTxState
|
override var state: VerificationTxState
|
||||||
get() {
|
get() {
|
||||||
refreshData()
|
refreshData()
|
||||||
val inner = this.inner
|
val inner = inner
|
||||||
val cancelInfo = inner?.cancelInfo
|
val cancelInfo = inner?.cancelInfo
|
||||||
|
|
||||||
return if (inner != null) {
|
return if (inner != null) {
|
||||||
@ -114,22 +114,22 @@ internal class QrCodeVerification(
|
|||||||
|
|
||||||
/** Get the unique id of this verification */
|
/** Get the unique id of this verification */
|
||||||
override val transactionId: String
|
override val transactionId: String
|
||||||
get() = this.request.flowId()
|
get() = request.flowId()
|
||||||
|
|
||||||
/** Get the user id of the other user participating in this verification flow */
|
/** Get the user id of the other user participating in this verification flow */
|
||||||
override val otherUserId: String
|
override val otherUserId: String
|
||||||
get() = this.request.otherUser()
|
get() = request.otherUser()
|
||||||
|
|
||||||
/** Get the device id of the other user's device participating in this verification flow */
|
/** Get the device id of the other user's device participating in this verification flow */
|
||||||
override var otherDeviceId: String?
|
override var otherDeviceId: String?
|
||||||
get() = this.request.otherDeviceId()
|
get() = request.otherDeviceId()
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
set(value) {
|
set(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Did the other side initiate this verification flow */
|
/** Did the other side initiate this verification flow */
|
||||||
override val isIncoming: Boolean
|
override val isIncoming: Boolean
|
||||||
get() = !this.request.weStarted()
|
get() = !request.weStarted()
|
||||||
|
|
||||||
/** Cancel the verification flow
|
/** Cancel the verification flow
|
||||||
*
|
*
|
||||||
@ -140,7 +140,7 @@ internal class QrCodeVerification(
|
|||||||
*
|
*
|
||||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||||
* */
|
* */
|
||||||
override fun cancel() {
|
override suspend fun cancel() {
|
||||||
cancelHelper(CancelCode.User)
|
cancelHelper(CancelCode.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,13 +155,13 @@ internal class QrCodeVerification(
|
|||||||
*
|
*
|
||||||
* @param code The cancel code that should be given as the reason for the cancellation.
|
* @param code The cancel code that should be given as the reason for the cancellation.
|
||||||
* */
|
* */
|
||||||
override fun cancel(code: CancelCode) {
|
override suspend fun cancel(code: CancelCode) {
|
||||||
cancelHelper(code)
|
cancelHelper(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Is this verification happening over to-device messages */
|
/** Is this verification happening over to-device messages */
|
||||||
override fun isToDeviceTransport(): Boolean {
|
override fun isToDeviceTransport(): Boolean {
|
||||||
return this.request.roomId() == null
|
return request.roomId() == null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Confirm the QR code verification
|
/** Confirm the QR code verification
|
||||||
@ -174,36 +174,36 @@ internal class QrCodeVerification(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
private suspend fun confirm() {
|
private suspend fun confirm() {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(coroutineDispatchers.io) {
|
||||||
machine.confirmVerification(request.otherUser(), request.flowId())
|
machine.confirmVerification(request.otherUser(), request.flowId())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
this.sender.sendVerificationRequest(result.request)
|
for (verificationRequest in result.requests) {
|
||||||
dispatchTxUpdated()
|
sender.sendVerificationRequest(verificationRequest)
|
||||||
|
|
||||||
val signatureRequest = result.signatureRequest
|
|
||||||
|
|
||||||
if (signatureRequest != null) {
|
|
||||||
this.sender.sendSignatureUpload(signatureRequest)
|
|
||||||
}
|
}
|
||||||
|
val signatureRequest = result.signatureRequest
|
||||||
|
if (signatureRequest != null) {
|
||||||
|
sender.sendSignatureUpload(signatureRequest)
|
||||||
|
}
|
||||||
|
dispatchTxUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelHelper(code: CancelCode) {
|
private suspend fun cancelHelper(code: CancelCode) {
|
||||||
val request = this.machine.cancelVerification(this.request.otherUser(), this.request.flowId(), code.value)
|
val request = machine.cancelVerification(request.otherUser(), request.flowId(), code.value)
|
||||||
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
runBlocking { sender.sendVerificationRequest(request) }
|
sender.sendVerificationRequest(request)
|
||||||
dispatchTxUpdated()
|
dispatchTxUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch fresh data from the Rust side for our verification flow */
|
/** Fetch fresh data from the Rust side for our verification flow */
|
||||||
private fun refreshData() {
|
private fun refreshData() {
|
||||||
when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) {
|
when (val verification = machine.getVerification(request.otherUser(), request.flowId())) {
|
||||||
is Verification.QrCodeV1 -> {
|
is Verification.QrCodeV1 -> {
|
||||||
this.inner = verification.qrcode
|
inner = verification.qrcode
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
@ -31,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
|||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RustCrossSigningService @Inject constructor(
|
internal class RustCrossSigningService @Inject constructor(
|
||||||
@ -45,30 +41,30 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Is our own device signed by our own cross signing identity
|
* Is our own device signed by our own cross signing identity
|
||||||
*/
|
*/
|
||||||
override fun isCrossSigningVerified(): Boolean {
|
override suspend fun isCrossSigningVerified(): Boolean {
|
||||||
return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) {
|
return when (val identity = olmMachine.getIdentity(olmMachine.userId())) {
|
||||||
is OwnUserIdentity -> identity.trustsOurOwnDevice()
|
is OwnUserIdentity -> identity.trustsOurOwnDevice()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isUserTrusted(otherUserId: String): Boolean {
|
override suspend fun isUserTrusted(otherUserId: String): Boolean {
|
||||||
// This seems to be used only in tests.
|
// This seems to be used only in tests.
|
||||||
return this.checkUserTrust(otherUserId).isVerified()
|
return checkUserTrust(otherUserId).isVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will not force a download of the key, but will verify signatures trust chain.
|
* Will not force a download of the key, but will verify signatures trust chain.
|
||||||
* Checks that my trusted user key has signed the other user UserKey
|
* Checks that my trusted user key has signed the other user UserKey
|
||||||
*/
|
*/
|
||||||
override fun checkUserTrust(otherUserId: String): UserTrustResult {
|
override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
|
||||||
val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }
|
val identity = olmMachine.getIdentity(olmMachine.userId())
|
||||||
|
|
||||||
// While UserTrustResult has many different states, they are by the callers
|
// While UserTrustResult has many different states, they are by the callers
|
||||||
// converted to a boolean value immediately, thus we don't need to support
|
// converted to a boolean value immediately, thus we don't need to support
|
||||||
// all the values.
|
// all the values.
|
||||||
return if (identity != null) {
|
return if (identity != null) {
|
||||||
val verified = runBlocking { identity.verified() }
|
val verified = identity.verified()
|
||||||
|
|
||||||
if (verified) {
|
if (verified) {
|
||||||
UserTrustResult.Success
|
UserTrustResult.Success
|
||||||
@ -84,8 +80,8 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
* Initialize cross signing for this user.
|
* Initialize cross signing for this user.
|
||||||
* Users needs to enter credentials
|
* Users needs to enter credentials
|
||||||
*/
|
*/
|
||||||
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
|
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||||
runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) }
|
olmMachine.bootstrapCrossSigning(uiaInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,26 +104,26 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
|
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
|
||||||
*/
|
*/
|
||||||
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||||
return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() }
|
return olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
override fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>> {
|
||||||
return runBlocking { olmMachine.getLiveUserIdentity(userId) }
|
return olmMachine.getLiveUserIdentity(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get our own public cross signing keys */
|
/** Get our own public cross signing keys */
|
||||||
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||||
return getUserCrossSigningKeys(olmMachine.userId())
|
return getUserCrossSigningKeys(olmMachine.userId())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get our own private cross signing keys */
|
/** Get our own private cross signing keys */
|
||||||
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||||
return runBlocking { olmMachine.exportCrossSigningKeys() }
|
return olmMachine.exportCrossSigningKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
override fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
|
||||||
return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() }
|
return olmMachine.getLivePrivateCrossSigningKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,34 +132,32 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
* Returning true means that we have the private self-signing and user-signing keys at hand.
|
* Returning true means that we have the private self-signing and user-signing keys at hand.
|
||||||
*/
|
*/
|
||||||
override fun canCrossSign(): Boolean {
|
override fun canCrossSign(): Boolean {
|
||||||
val status = this.olmMachine.crossSigningStatus()
|
val status = olmMachine.crossSigningStatus()
|
||||||
|
|
||||||
return status.hasSelfSigning && status.hasUserSigning
|
return status.hasSelfSigning && status.hasUserSigning
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allPrivateKeysKnown(): Boolean {
|
override fun allPrivateKeysKnown(): Boolean {
|
||||||
val status = this.olmMachine.crossSigningStatus()
|
val status = olmMachine.crossSigningStatus()
|
||||||
|
|
||||||
return status.hasMaster && status.hasSelfSigning && status.hasUserSigning
|
return status.hasMaster && status.hasSelfSigning && status.hasUserSigning
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
||||||
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
override suspend fun trustUser(otherUserId: String) {
|
||||||
// This is only used in a test
|
// This is only used in a test
|
||||||
val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) }
|
val userIdentity = olmMachine.getIdentity(otherUserId)
|
||||||
|
|
||||||
if (userIdentity != null) {
|
if (userIdentity != null) {
|
||||||
runBlocking { userIdentity.verify() }
|
userIdentity.verify()
|
||||||
callback.onSuccess(Unit)
|
|
||||||
} else {
|
} else {
|
||||||
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
|
throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mark our own master key as trusted */
|
/** Mark our own master key as trusted */
|
||||||
override fun markMyMasterKeyAsTrusted() {
|
override suspend fun markMyMasterKeyAsTrusted() {
|
||||||
// This doesn't seem to be used?
|
// This doesn't seem to be used?
|
||||||
this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback())
|
trustUser(olmMachine.userId())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,10 +165,8 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
override suspend fun trustDevice(deviceId: String) {
|
override suspend fun trustDevice(deviceId: String) {
|
||||||
val device = olmMachine.getDevice(olmMachine.userId(), deviceId)
|
val device = olmMachine.getDevice(olmMachine.userId(), deviceId)
|
||||||
|
if (device != null) {
|
||||||
return if (device != null) {
|
|
||||||
val verified = device.verify()
|
val verified = device.verify()
|
||||||
|
|
||||||
if (verified) {
|
if (verified) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@ -192,15 +184,15 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
* using the self-signing key for our own devices or using the user-signing key and the master
|
* using the self-signing key for our own devices or using the user-signing key and the master
|
||||||
* key of another user.
|
* key of another user.
|
||||||
*/
|
*/
|
||||||
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||||
val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) }
|
val device = olmMachine.getDevice(otherUserId, otherDeviceId)
|
||||||
|
|
||||||
return if (device != null) {
|
return if (device != null) {
|
||||||
// TODO i don't quite understand the semantics here and there are no docs for
|
// TODO i don't quite understand the semantics here and there are no docs for
|
||||||
// DeviceTrustResult, what do the different result types mean exactly,
|
// DeviceTrustResult, what do the different result types mean exactly,
|
||||||
// do you return success only if the Device is cross signing verified?
|
// do you return success only if the Device is cross signing verified?
|
||||||
// what about the local trust if it isn't? why is the local trust passed as an argument here?
|
// what about the local trust if it isn't? why is the local trust passed as an argument here?
|
||||||
DeviceTrustResult.Success(runBlocking { device.trustLevel() })
|
DeviceTrustResult.Success(device.trustLevel())
|
||||||
} else {
|
} else {
|
||||||
DeviceTrustResult.UnknownDevice(otherDeviceId)
|
DeviceTrustResult.UnknownDevice(otherDeviceId)
|
||||||
}
|
}
|
||||||
@ -215,7 +207,7 @@ internal class RustCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSecretUSKGossip(uskPrivateKey: String) {
|
override fun onSecretUSKGossip(uskPrivateKey: String) {
|
||||||
// And this.
|
// And
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {
|
override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||||
|
@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
|
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
|
||||||
import uniffi.olm.CryptoStoreException
|
import uniffi.olm.CryptoStoreException
|
||||||
@ -37,6 +37,7 @@ internal class SasVerification(
|
|||||||
private val machine: OlmMachine,
|
private val machine: OlmMachine,
|
||||||
private var inner: Sas,
|
private var inner: Sas,
|
||||||
private val sender: RequestSender,
|
private val sender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
listeners: ArrayList<VerificationService.Listener>
|
listeners: ArrayList<VerificationService.Listener>
|
||||||
) :
|
) :
|
||||||
SasVerificationTransaction {
|
SasVerificationTransaction {
|
||||||
@ -44,27 +45,27 @@ internal class SasVerification(
|
|||||||
|
|
||||||
private fun dispatchTxUpdated() {
|
private fun dispatchTxUpdated() {
|
||||||
refreshData()
|
refreshData()
|
||||||
this.dispatcher.dispatchTxUpdated(this)
|
dispatcher.dispatchTxUpdated(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The user ID of the other user that is participating in this verification flow */
|
/** The user ID of the other user that is participating in this verification flow */
|
||||||
override val otherUserId: String = this.inner.otherUserId
|
override val otherUserId: String = inner.otherUserId
|
||||||
|
|
||||||
/** Get the device id of the other user's device participating in this verification flow */
|
/** Get the device id of the other user's device participating in this verification flow */
|
||||||
override var otherDeviceId: String?
|
override var otherDeviceId: String?
|
||||||
get() = this.inner.otherDeviceId
|
get() = inner.otherDeviceId
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
set(value) {
|
set(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Did the other side initiate this verification flow */
|
/** Did the other side initiate this verification flow */
|
||||||
override val isIncoming: Boolean
|
override val isIncoming: Boolean
|
||||||
get() = !this.inner.weStarted
|
get() = !inner.weStarted
|
||||||
|
|
||||||
override var state: VerificationTxState
|
override var state: VerificationTxState
|
||||||
get() {
|
get() {
|
||||||
refreshData()
|
refreshData()
|
||||||
val cancelInfo = this.inner.cancelInfo
|
val cancelInfo = inner.cancelInfo
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
cancelInfo != null -> {
|
cancelInfo != null -> {
|
||||||
@ -84,7 +85,7 @@ internal class SasVerification(
|
|||||||
|
|
||||||
/** Get the unique id of this verification */
|
/** Get the unique id of this verification */
|
||||||
override val transactionId: String
|
override val transactionId: String
|
||||||
get() = this.inner.flowId
|
get() = inner.flowId
|
||||||
|
|
||||||
/** Cancel the verification flow
|
/** Cancel the verification flow
|
||||||
*
|
*
|
||||||
@ -95,8 +96,8 @@ internal class SasVerification(
|
|||||||
*
|
*
|
||||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||||
* */
|
* */
|
||||||
override fun cancel() {
|
override suspend fun cancel() {
|
||||||
this.cancelHelper(CancelCode.User)
|
cancelHelper(CancelCode.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cancel the verification flow
|
/** Cancel the verification flow
|
||||||
@ -110,8 +111,8 @@ internal class SasVerification(
|
|||||||
*
|
*
|
||||||
* @param code The cancel code that should be given as the reason for the cancellation.
|
* @param code The cancel code that should be given as the reason for the cancellation.
|
||||||
* */
|
* */
|
||||||
override fun cancel(code: CancelCode) {
|
override suspend fun cancel(code: CancelCode) {
|
||||||
this.cancelHelper(code)
|
cancelHelper(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cancel the verification flow
|
/** Cancel the verification flow
|
||||||
@ -123,25 +124,17 @@ internal class SasVerification(
|
|||||||
*
|
*
|
||||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||||
*/
|
*/
|
||||||
override fun shortCodeDoesNotMatch() {
|
override suspend fun shortCodeDoesNotMatch() {
|
||||||
this.cancelHelper(CancelCode.MismatchedSas)
|
cancelHelper(CancelCode.MismatchedSas)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Is this verification happening over to-device messages */
|
/** Is this verification happening over to-device messages */
|
||||||
override fun isToDeviceTransport(): Boolean = this.inner.roomId == null
|
override fun isToDeviceTransport(): Boolean = inner.roomId == null
|
||||||
|
|
||||||
/** Does the verification flow support showing decimals as the short auth string */
|
|
||||||
override fun supportsDecimal(): Boolean {
|
|
||||||
// This is ignored anyways, throw it away?
|
|
||||||
// The spec also mandates that devices support at least decimal and
|
|
||||||
// the rust-sdk cancels if devices don't support it
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Does the verification flow support showing emojis as the short auth string */
|
/** Does the verification flow support showing emojis as the short auth string */
|
||||||
override fun supportsEmoji(): Boolean {
|
override fun supportsEmoji(): Boolean {
|
||||||
refreshData()
|
refreshData()
|
||||||
return this.inner.supportsEmoji
|
return inner.supportsEmoji
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Confirm that the short authentication code matches on both sides
|
/** Confirm that the short authentication code matches on both sides
|
||||||
@ -153,8 +146,8 @@ internal class SasVerification(
|
|||||||
* This method is a noop if we're not yet in a presentable state, i.e. we didn't receive
|
* This method is a noop if we're not yet in a presentable state, i.e. we didn't receive
|
||||||
* a m.key.verification.key event from the other side or we're cancelled.
|
* a m.key.verification.key event from the other side or we're cancelled.
|
||||||
*/
|
*/
|
||||||
override fun userHasVerifiedShortCode() {
|
override suspend fun userHasVerifiedShortCode() {
|
||||||
runBlocking { confirm() }
|
confirm()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Accept the verification flow, signaling the other side that we do want to verify
|
/** Accept the verification flow, signaling the other side that we do want to verify
|
||||||
@ -165,8 +158,8 @@ internal class SasVerification(
|
|||||||
* This method is a noop if we send the start event out or if the verification has already
|
* This method is a noop if we send the start event out or if the verification has already
|
||||||
* been accepted.
|
* been accepted.
|
||||||
*/
|
*/
|
||||||
override fun acceptVerification() {
|
override suspend fun acceptVerification() {
|
||||||
runBlocking { accept() }
|
accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the decimal representation of the short auth string
|
/** Get the decimal representation of the short auth string
|
||||||
@ -176,7 +169,7 @@ internal class SasVerification(
|
|||||||
* in a presentable state.
|
* in a presentable state.
|
||||||
*/
|
*/
|
||||||
override fun getDecimalCodeRepresentation(): String {
|
override fun getDecimalCodeRepresentation(): String {
|
||||||
val decimals = this.machine.getDecimals(this.inner.otherUserId, this.inner.flowId)
|
val decimals = machine.getDecimals(inner.otherUserId, inner.flowId)
|
||||||
|
|
||||||
return decimals?.joinToString(" ") ?: ""
|
return decimals?.joinToString(" ") ?: ""
|
||||||
}
|
}
|
||||||
@ -188,52 +181,51 @@ internal class SasVerification(
|
|||||||
* state.
|
* state.
|
||||||
*/
|
*/
|
||||||
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
|
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
|
||||||
val emojiIndex = this.machine.getEmojiIndex(this.inner.otherUserId, this.inner.flowId)
|
val emojiIndex = machine.getEmojiIndex(inner.otherUserId, inner.flowId)
|
||||||
|
|
||||||
return emojiIndex?.map { getEmojiForCode(it) } ?: listOf()
|
return emojiIndex?.map { getEmojiForCode(it) } ?: listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal suspend fun accept() {
|
internal suspend fun accept() {
|
||||||
val request = this.machine.acceptSasVerification(this.inner.otherUserId, inner.flowId)
|
val request = machine.acceptSasVerification(inner.otherUserId, inner.flowId)
|
||||||
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
this.sender.sendVerificationRequest(request)
|
sender.sendVerificationRequest(request)
|
||||||
dispatchTxUpdated()
|
dispatchTxUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
private suspend fun confirm() {
|
private suspend fun confirm() {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(coroutineDispatchers.io) {
|
||||||
machine.confirmVerification(inner.otherUserId, inner.flowId)
|
machine.confirmVerification(inner.otherUserId, inner.flowId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
this.sender.sendVerificationRequest(result.request)
|
for (verificationRequest in result.requests) {
|
||||||
dispatchTxUpdated()
|
sender.sendVerificationRequest(verificationRequest)
|
||||||
|
|
||||||
val signatureRequest = result.signatureRequest
|
|
||||||
|
|
||||||
if (signatureRequest != null) {
|
|
||||||
this.sender.sendSignatureUpload(signatureRequest)
|
|
||||||
}
|
}
|
||||||
|
val signatureRequest = result.signatureRequest
|
||||||
|
if (signatureRequest != null) {
|
||||||
|
sender.sendSignatureUpload(signatureRequest)
|
||||||
|
}
|
||||||
|
dispatchTxUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelHelper(code: CancelCode) {
|
private suspend fun cancelHelper(code: CancelCode) {
|
||||||
val request = this.machine.cancelVerification(this.inner.otherUserId, inner.flowId, code.value)
|
val request = machine.cancelVerification(inner.otherUserId, inner.flowId, code.value)
|
||||||
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
runBlocking { sender.sendVerificationRequest(request) }
|
sender.sendVerificationRequest(request)
|
||||||
dispatchTxUpdated()
|
dispatchTxUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch fresh data from the Rust side for our verification flow */
|
/** Fetch fresh data from the Rust side for our verification flow */
|
||||||
private fun refreshData() {
|
private fun refreshData() {
|
||||||
when (val verification = this.machine.getVerification(this.inner.otherUserId, this.inner.flowId)) {
|
when (val verification = machine.getVerification(inner.otherUserId, inner.flowId)) {
|
||||||
is Verification.SasV1 -> {
|
is Verification.SasV1 -> {
|
||||||
this.inner = verification.sas
|
inner = verification.sas
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||||
import uniffi.olm.CryptoStoreException
|
import uniffi.olm.CryptoStoreException
|
||||||
import uniffi.olm.SignatureException
|
import uniffi.olm.SignatureException
|
||||||
@ -65,7 +65,7 @@ sealed class UserIdentities {
|
|||||||
/**
|
/**
|
||||||
* Convert the identity into a MxCrossSigningInfo class.
|
* Convert the identity into a MxCrossSigningInfo class.
|
||||||
*/
|
*/
|
||||||
abstract fun toMxCrossSigningInfo(): MXCrossSigningInfo
|
abstract suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,11 +80,12 @@ internal class OwnUserIdentity(
|
|||||||
private val userSigningKey: CryptoCrossSigningKey,
|
private val userSigningKey: CryptoCrossSigningKey,
|
||||||
private val trustsOurOwnDevice: Boolean,
|
private val trustsOurOwnDevice: Boolean,
|
||||||
private val olmMachine: OlmMachine,
|
private val olmMachine: OlmMachine,
|
||||||
private val requestSender: RequestSender) : UserIdentities() {
|
private val requestSender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
|
||||||
/**
|
/**
|
||||||
* Our own user id.
|
* Our own user id.
|
||||||
*/
|
*/
|
||||||
override fun userId() = this.userId
|
override fun userId() = userId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually verify our user identity.
|
* Manually verify our user identity.
|
||||||
@ -95,8 +96,8 @@ internal class OwnUserIdentity(
|
|||||||
*/
|
*/
|
||||||
@Throws(SignatureException::class)
|
@Throws(SignatureException::class)
|
||||||
override suspend fun verify() {
|
override suspend fun verify() {
|
||||||
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
|
val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
|
||||||
this.requestSender.sendSignatureUpload(request)
|
requestSender.sendSignatureUpload(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,13 +107,13 @@ internal class OwnUserIdentity(
|
|||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
override suspend fun verified(): Boolean {
|
override suspend fun verified(): Boolean {
|
||||||
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
|
return withContext(coroutineDispatchers.io) { olmMachine.inner().isIdentityVerified(userId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the identity trust our own device.
|
* Does the identity trust our own device.
|
||||||
*/
|
*/
|
||||||
fun trustsOurOwnDevice() = this.trustsOurOwnDevice
|
fun trustsOurOwnDevice() = trustsOurOwnDevice
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request an interactive verification to begin
|
* Request an interactive verification to begin
|
||||||
@ -133,32 +134,33 @@ internal class OwnUserIdentity(
|
|||||||
@Throws(CryptoStoreException::class)
|
@Throws(CryptoStoreException::class)
|
||||||
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
|
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
|
||||||
val stringMethods = prepareMethods(methods)
|
val stringMethods = prepareMethods(methods)
|
||||||
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
|
val result = olmMachine.inner().requestSelfVerification(stringMethods)
|
||||||
this.requestSender.sendVerificationRequest(result!!.request)
|
requestSender.sendVerificationRequest(result!!.request)
|
||||||
|
|
||||||
return VerificationRequest(
|
return VerificationRequest(
|
||||||
this.olmMachine.inner(),
|
machine = olmMachine.inner(),
|
||||||
result.verification,
|
inner = result.verification,
|
||||||
this.requestSender,
|
sender = requestSender,
|
||||||
this.olmMachine.verificationListeners
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = olmMachine.verificationListeners
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the identity into a MxCrossSigningInfo class.
|
* Convert the identity into a MxCrossSigningInfo class.
|
||||||
*/
|
*/
|
||||||
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
||||||
val masterKey = this.masterKey
|
val masterKey = masterKey
|
||||||
val selfSigningKey = this.selfSigningKey
|
val selfSigningKey = selfSigningKey
|
||||||
val userSigningKey = this.userSigningKey
|
val userSigningKey = userSigningKey
|
||||||
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
|
val trustLevel = DeviceTrustLevel(verified(), false)
|
||||||
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
|
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
|
||||||
masterKey.trustLevel = trustLevel
|
masterKey.trustLevel = trustLevel
|
||||||
selfSigningKey.trustLevel = trustLevel
|
selfSigningKey.trustLevel = trustLevel
|
||||||
userSigningKey.trustLevel = trustLevel
|
userSigningKey.trustLevel = trustLevel
|
||||||
|
|
||||||
val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey)
|
val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey)
|
||||||
return MXCrossSigningInfo(this.userId, crossSigningKeys)
|
return MXCrossSigningInfo(userId, crossSigningKeys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,11 +174,12 @@ internal class UserIdentity(
|
|||||||
private val masterKey: CryptoCrossSigningKey,
|
private val masterKey: CryptoCrossSigningKey,
|
||||||
private val selfSigningKey: CryptoCrossSigningKey,
|
private val selfSigningKey: CryptoCrossSigningKey,
|
||||||
private val olmMachine: OlmMachine,
|
private val olmMachine: OlmMachine,
|
||||||
private val requestSender: RequestSender) : UserIdentities() {
|
private val requestSender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers) : UserIdentities() {
|
||||||
/**
|
/**
|
||||||
* The unique ID of the user that this identity belongs to.
|
* The unique ID of the user that this identity belongs to.
|
||||||
*/
|
*/
|
||||||
override fun userId() = this.userId
|
override fun userId() = userId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually verify this user identity.
|
* Manually verify this user identity.
|
||||||
@ -189,8 +192,8 @@ internal class UserIdentity(
|
|||||||
*/
|
*/
|
||||||
@Throws(SignatureException::class)
|
@Throws(SignatureException::class)
|
||||||
override suspend fun verify() {
|
override suspend fun verify() {
|
||||||
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
|
val request = withContext(coroutineDispatchers.computation) { olmMachine.inner().verifyIdentity(userId) }
|
||||||
this.requestSender.sendSignatureUpload(request)
|
requestSender.sendSignatureUpload(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +202,7 @@ internal class UserIdentity(
|
|||||||
* @return True if the identity is considered to be verified and trusted, false otherwise.
|
* @return True if the identity is considered to be verified and trusted, false otherwise.
|
||||||
*/
|
*/
|
||||||
override suspend fun verified(): Boolean {
|
override suspend fun verified(): Boolean {
|
||||||
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
|
return withContext(coroutineDispatchers.io) { olmMachine.inner().isIdentityVerified(userId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,32 +235,33 @@ internal class UserIdentity(
|
|||||||
transactionId: String
|
transactionId: String
|
||||||
): VerificationRequest {
|
): VerificationRequest {
|
||||||
val stringMethods = prepareMethods(methods)
|
val stringMethods = prepareMethods(methods)
|
||||||
val content = this.olmMachine.inner().verificationRequestContent(this.userId, stringMethods)!!
|
val content = olmMachine.inner().verificationRequestContent(userId, stringMethods)!!
|
||||||
|
|
||||||
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId)
|
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId).eventId
|
||||||
|
|
||||||
val innerRequest = this.olmMachine.inner().requestVerification(this.userId, roomId, eventID, stringMethods)!!
|
val innerRequest = olmMachine.inner().requestVerification(userId, roomId, eventID, stringMethods)!!
|
||||||
|
|
||||||
return VerificationRequest(
|
return VerificationRequest(
|
||||||
this.olmMachine.inner(),
|
machine = olmMachine.inner(),
|
||||||
innerRequest,
|
inner = innerRequest,
|
||||||
this.requestSender,
|
sender = requestSender,
|
||||||
this.olmMachine.verificationListeners
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = olmMachine.verificationListeners
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the identity into a MxCrossSigningInfo class.
|
* Convert the identity into a MxCrossSigningInfo class.
|
||||||
*/
|
*/
|
||||||
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
override suspend fun toMxCrossSigningInfo(): MXCrossSigningInfo {
|
||||||
// val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey)
|
// val crossSigningKeys = listOf(masterKey, selfSigningKey)
|
||||||
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
|
val trustLevel = DeviceTrustLevel(verified(), false)
|
||||||
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
|
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
|
||||||
masterKey.trustLevel = trustLevel
|
masterKey.trustLevel = trustLevel
|
||||||
selfSigningKey.trustLevel = trustLevel
|
selfSigningKey.trustLevel = trustLevel
|
||||||
return MXCrossSigningInfo(this.userId, listOf(
|
return MXCrossSigningInfo(userId, listOf(
|
||||||
this.masterKey.also { it.trustLevel = trustLevel },
|
masterKey.also { it.trustLevel = trustLevel },
|
||||||
this.selfSigningKey.also { it.trustLevel = trustLevel }
|
selfSigningKey.also { it.trustLevel = trustLevel }
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ package org.matrix.android.sdk.internal.crypto
|
|||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
||||||
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
|
|||||||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uniffi.olm.OlmMachine
|
import uniffi.olm.OlmMachine
|
||||||
@ -45,6 +46,7 @@ internal class VerificationRequest(
|
|||||||
private val machine: OlmMachine,
|
private val machine: OlmMachine,
|
||||||
private var inner: VerificationRequest,
|
private var inner: VerificationRequest,
|
||||||
private val sender: RequestSender,
|
private val sender: RequestSender,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val listeners: ArrayList<VerificationService.Listener>
|
private val listeners: ArrayList<VerificationService.Listener>
|
||||||
) {
|
) {
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
@ -53,7 +55,7 @@ internal class VerificationRequest(
|
|||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
try {
|
try {
|
||||||
it.verificationRequestUpdated(this.toPendingVerificationRequest())
|
it.verificationRequestUpdated(toPendingVerificationRequest())
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.e(e, "## Error while notifying listeners")
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
}
|
}
|
||||||
@ -68,12 +70,12 @@ internal class VerificationRequest(
|
|||||||
* event that initiated the flow.
|
* event that initiated the flow.
|
||||||
*/
|
*/
|
||||||
internal fun flowId(): String {
|
internal fun flowId(): String {
|
||||||
return this.inner.flowId
|
return inner.flowId
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The user ID of the other user that is participating in this verification flow */
|
/** The user ID of the other user that is participating in this verification flow */
|
||||||
internal fun otherUser(): String {
|
internal fun otherUser(): String {
|
||||||
return this.inner.otherUserId
|
return inner.otherUserId
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The device ID of the other user's device that is participating in this verification flow
|
/** The device ID of the other user's device that is participating in this verification flow
|
||||||
@ -83,12 +85,12 @@ internal class VerificationRequest(
|
|||||||
* */
|
* */
|
||||||
internal fun otherDeviceId(): String? {
|
internal fun otherDeviceId(): String? {
|
||||||
refreshData()
|
refreshData()
|
||||||
return this.inner.otherDeviceId
|
return inner.otherDeviceId
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Did we initiate this verification flow */
|
/** Did we initiate this verification flow */
|
||||||
internal fun weStarted(): Boolean {
|
internal fun weStarted(): Boolean {
|
||||||
return this.inner.weStarted
|
return inner.weStarted
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the id of the room where this verification is happening
|
/** Get the id of the room where this verification is happening
|
||||||
@ -96,7 +98,7 @@ internal class VerificationRequest(
|
|||||||
* Will be null if the verification is not happening inside a room.
|
* Will be null if the verification is not happening inside a room.
|
||||||
*/
|
*/
|
||||||
internal fun roomId(): String? {
|
internal fun roomId(): String? {
|
||||||
return this.inner.roomId
|
return inner.roomId
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Did the non-initiating side respond with a m.key.verification.read event
|
/** Did the non-initiating side respond with a m.key.verification.read event
|
||||||
@ -107,13 +109,13 @@ internal class VerificationRequest(
|
|||||||
*/
|
*/
|
||||||
internal fun isReady(): Boolean {
|
internal fun isReady(): Boolean {
|
||||||
refreshData()
|
refreshData()
|
||||||
return this.inner.isReady
|
return inner.isReady
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Did we advertise that we're able to scan QR codes */
|
/** Did we advertise that we're able to scan QR codes */
|
||||||
internal fun canScanQrCodes(): Boolean {
|
internal fun canScanQrCodes(): Boolean {
|
||||||
refreshData()
|
refreshData()
|
||||||
return this.inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
|
return inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Accept the verification request advertising the given methods as supported
|
/** Accept the verification request advertising the given methods as supported
|
||||||
@ -132,15 +134,15 @@ internal class VerificationRequest(
|
|||||||
suspend fun acceptWithMethods(methods: List<VerificationMethod>) {
|
suspend fun acceptWithMethods(methods: List<VerificationMethod>) {
|
||||||
val stringMethods = prepareMethods(methods)
|
val stringMethods = prepareMethods(methods)
|
||||||
|
|
||||||
val request = this.machine.acceptVerificationRequest(
|
val request = machine.acceptVerificationRequest(
|
||||||
this.inner.otherUserId,
|
inner.otherUserId,
|
||||||
this.inner.flowId,
|
inner.flowId,
|
||||||
stringMethods
|
stringMethods
|
||||||
)
|
)
|
||||||
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
this.sender.sendVerificationRequest(request)
|
sender.sendVerificationRequest(request)
|
||||||
this.dispatchRequestUpdated()
|
dispatchRequestUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,12 +160,12 @@ internal class VerificationRequest(
|
|||||||
* emoji verification, or null if we can't yet transition into emoji verification.
|
* emoji verification, or null if we can't yet transition into emoji verification.
|
||||||
*/
|
*/
|
||||||
internal suspend fun startSasVerification(): SasVerification? {
|
internal suspend fun startSasVerification(): SasVerification? {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(coroutineDispatchers.io) {
|
||||||
val result = machine.startSasVerification(inner.otherUserId, inner.flowId)
|
val result = machine.startSasVerification(inner.otherUserId, inner.flowId)
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
sender.sendVerificationRequest(result.request)
|
sender.sendVerificationRequest(result.request)
|
||||||
SasVerification(machine, result.sas, sender, listeners)
|
SasVerification(machine, result.sas, sender, coroutineDispatchers, listeners)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -187,10 +189,10 @@ internal class VerificationRequest(
|
|||||||
// TODO again, what's the deal with ISO_8859_1?
|
// TODO again, what's the deal with ISO_8859_1?
|
||||||
val byteArray = data.toByteArray(Charsets.ISO_8859_1)
|
val byteArray = data.toByteArray(Charsets.ISO_8859_1)
|
||||||
val encodedData = byteArray.toBase64NoPadding()
|
val encodedData = byteArray.toBase64NoPadding()
|
||||||
val result = this.machine.scanQrCode(this.otherUser(), this.flowId(), encodedData) ?: return null
|
val result = machine.scanQrCode(otherUser(), flowId(), encodedData) ?: return null
|
||||||
|
|
||||||
this.sender.sendVerificationRequest(result.request)
|
sender.sendVerificationRequest(result.request)
|
||||||
return QrCodeVerification(this.machine, this, result.qr, this.sender, this.listeners)
|
return QrCodeVerification(machine, this, result.qr, sender, coroutineDispatchers, listeners)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Transition into a QR code verification to display a QR code
|
/** Transition into a QR code verification to display a QR code
|
||||||
@ -211,15 +213,16 @@ internal class VerificationRequest(
|
|||||||
* QR code verification, or null if we can't yet transition into QR code verification.
|
* QR code verification, or null if we can't yet transition into QR code verification.
|
||||||
*/
|
*/
|
||||||
internal fun startQrVerification(): QrCodeVerification? {
|
internal fun startQrVerification(): QrCodeVerification? {
|
||||||
val qrcode = this.machine.startQrVerification(this.inner.otherUserId, this.inner.flowId)
|
val qrcode = machine.startQrVerification(inner.otherUserId, inner.flowId)
|
||||||
|
|
||||||
return if (qrcode != null) {
|
return if (qrcode != null) {
|
||||||
QrCodeVerification(
|
QrCodeVerification(
|
||||||
this.machine,
|
machine = machine,
|
||||||
this,
|
request = this,
|
||||||
qrcode,
|
inner = qrcode,
|
||||||
this.sender,
|
sender = sender,
|
||||||
this.listeners,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
|
listeners = listeners,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@ -237,24 +240,24 @@ internal class VerificationRequest(
|
|||||||
* The method turns into a noop, if the verification flow has already been cancelled.
|
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||||
*/
|
*/
|
||||||
internal suspend fun cancel() {
|
internal suspend fun cancel() {
|
||||||
val request = this.machine.cancelVerification(
|
val request = machine.cancelVerification(
|
||||||
this.inner.otherUserId,
|
inner.otherUserId,
|
||||||
this.inner.flowId,
|
inner.flowId,
|
||||||
CancelCode.User.value
|
CancelCode.User.value
|
||||||
)
|
)
|
||||||
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
this.sender.sendVerificationRequest(request)
|
sender.sendVerificationRequest(request)
|
||||||
this.dispatchRequestUpdated()
|
dispatchRequestUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch fresh data from the Rust side for our verification flow */
|
/** Fetch fresh data from the Rust side for our verification flow */
|
||||||
private fun refreshData() {
|
private fun refreshData() {
|
||||||
val request = this.machine.getVerificationRequest(this.inner.otherUserId, this.inner.flowId)
|
val request = machine.getVerificationRequest(inner.otherUserId, inner.flowId)
|
||||||
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
this.inner = request
|
inner = request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +272,7 @@ internal class VerificationRequest(
|
|||||||
*/
|
*/
|
||||||
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
|
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
|
||||||
refreshData()
|
refreshData()
|
||||||
val cancelInfo = this.inner.cancelInfo
|
val cancelInfo = inner.cancelInfo
|
||||||
val cancelCode =
|
val cancelCode =
|
||||||
if (cancelInfo != null) {
|
if (cancelInfo != null) {
|
||||||
safeValueOf(cancelInfo.cancelCode)
|
safeValueOf(cancelInfo.cancelCode)
|
||||||
@ -277,72 +280,72 @@ internal class VerificationRequest(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val ourMethods = this.inner.ourMethods
|
val ourMethods = inner.ourMethods
|
||||||
val theirMethods = this.inner.theirMethods
|
val theirMethods = inner.theirMethods
|
||||||
val otherDeviceId = this.inner.otherDeviceId
|
val otherDeviceId = inner.otherDeviceId
|
||||||
|
|
||||||
var requestInfo: ValidVerificationInfoRequest? = null
|
var requestInfo: ValidVerificationInfoRequest? = null
|
||||||
var readyInfo: ValidVerificationInfoReady? = null
|
var readyInfo: ValidVerificationInfoReady? = null
|
||||||
|
|
||||||
if (this.inner.weStarted && ourMethods != null) {
|
if (inner.weStarted && ourMethods != null) {
|
||||||
requestInfo =
|
requestInfo =
|
||||||
ValidVerificationInfoRequest(
|
ValidVerificationInfoRequest(
|
||||||
this.inner.flowId,
|
transactionId = inner.flowId,
|
||||||
this.machine.deviceId(),
|
fromDevice = machine.deviceId(),
|
||||||
ourMethods,
|
methods = ourMethods,
|
||||||
null,
|
timestamp = null,
|
||||||
)
|
)
|
||||||
} else if (!this.inner.weStarted && ourMethods != null) {
|
} else if (!inner.weStarted && ourMethods != null) {
|
||||||
readyInfo =
|
readyInfo =
|
||||||
ValidVerificationInfoReady(
|
ValidVerificationInfoReady(
|
||||||
this.inner.flowId,
|
transactionId = inner.flowId,
|
||||||
this.machine.deviceId(),
|
fromDevice = machine.deviceId(),
|
||||||
ourMethods,
|
methods = ourMethods,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
if (inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||||
readyInfo =
|
readyInfo =
|
||||||
ValidVerificationInfoReady(
|
ValidVerificationInfoReady(
|
||||||
this.inner.flowId,
|
transactionId = inner.flowId,
|
||||||
otherDeviceId,
|
fromDevice = otherDeviceId,
|
||||||
theirMethods,
|
methods = theirMethods,
|
||||||
)
|
)
|
||||||
} else if (!this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
} else if (!inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||||
requestInfo =
|
requestInfo =
|
||||||
ValidVerificationInfoRequest(
|
ValidVerificationInfoRequest(
|
||||||
this.inner.flowId,
|
transactionId = inner.flowId,
|
||||||
otherDeviceId,
|
fromDevice = otherDeviceId,
|
||||||
theirMethods,
|
methods = theirMethods,
|
||||||
System.currentTimeMillis(),
|
timestamp = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return PendingVerificationRequest(
|
return PendingVerificationRequest(
|
||||||
// Creation time
|
// Creation time
|
||||||
System.currentTimeMillis(),
|
ageLocalTs = System.currentTimeMillis(),
|
||||||
// Who initiated the request
|
// Who initiated the request
|
||||||
!this.inner.weStarted,
|
isIncoming = !inner.weStarted,
|
||||||
// Local echo id, what to do here?
|
// Local echo id, what to do here?
|
||||||
this.inner.flowId,
|
localId = inner.flowId,
|
||||||
// other user
|
// other user
|
||||||
this.inner.otherUserId,
|
otherUserId = inner.otherUserId,
|
||||||
// room id
|
// room id
|
||||||
this.inner.roomId,
|
roomId = inner.roomId,
|
||||||
// transaction id
|
// transaction id
|
||||||
this.inner.flowId,
|
transactionId = inner.flowId,
|
||||||
// val requestInfo: ValidVerificationInfoRequest? = null,
|
// val requestInfo: ValidVerificationInfoRequest? = null,
|
||||||
requestInfo,
|
requestInfo = requestInfo,
|
||||||
// val readyInfo: ValidVerificationInfoReady? = null,
|
// val readyInfo: ValidVerificationInfoReady? = null,
|
||||||
readyInfo,
|
readyInfo = readyInfo,
|
||||||
// cancel code if there is one
|
// cancel code if there is one
|
||||||
cancelCode,
|
cancelConclusion = cancelCode,
|
||||||
// are we done/successful
|
// are we done/successful
|
||||||
this.inner.isDone,
|
isSuccessful = inner.isDone,
|
||||||
// did another device answer the request
|
// did another device answer the request
|
||||||
this.inner.isPassive,
|
handledByOtherSession = inner.isPassive,
|
||||||
// devices that should receive the events we send out
|
// devices that should receive the events we send out
|
||||||
null,
|
targetDevices = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import android.os.Looper
|
|||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
@ -41,7 +40,6 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
|||||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager
|
import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager
|
||||||
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
|
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
|
||||||
import org.matrix.android.sdk.internal.crypto.RequestSender
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||||
@ -53,6 +51,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
|||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
|
import org.matrix.android.sdk.internal.crypto.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
@ -95,7 +94,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
|
|
||||||
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
||||||
|
|
||||||
private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
private val importScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.main)
|
||||||
|
|
||||||
private var keysBackupStateListener: KeysBackupStateListener? = null
|
private var keysBackupStateListener: KeysBackupStateListener? = null
|
||||||
|
|
||||||
@ -237,7 +236,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canRestoreKeys(): Boolean {
|
override suspend fun canRestoreKeys(): Boolean {
|
||||||
val keyCountOnServer = keysBackupVersion?.count ?: return false
|
val keyCountOnServer = keysBackupVersion?.count ?: return false
|
||||||
val keyCountLocally = getTotalNumbersOfKeys()
|
val keyCountLocally = getTotalNumbersOfKeys()
|
||||||
|
|
||||||
@ -246,11 +245,11 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
return keyCountLocally < keyCountOnServer
|
return keyCountLocally < keyCountOnServer
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTotalNumbersOfKeys(): Int {
|
override suspend fun getTotalNumbersOfKeys(): Int {
|
||||||
return olmMachine.roomKeyCounts().total.toInt()
|
return olmMachine.roomKeyCounts().total.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTotalNumbersOfBackedUpKeys(): Int {
|
override suspend fun getTotalNumbersOfBackedUpKeys(): Int {
|
||||||
return olmMachine.roomKeyCounts().backedUp.toInt()
|
return olmMachine.roomKeyCounts().backedUp.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +404,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBackupProgress(progressListener: ProgressListener) {
|
override suspend fun getBackupProgress(progressListener: ProgressListener) {
|
||||||
val backedUpKeys = getTotalNumbersOfBackedUpKeys()
|
val backedUpKeys = getTotalNumbersOfBackedUpKeys()
|
||||||
val total = getTotalNumbersOfKeys()
|
val total = getTotalNumbersOfKeys()
|
||||||
|
|
||||||
@ -490,7 +489,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
||||||
|
|
||||||
return withContext(coroutineDispatchers.computation) {
|
return withContext(coroutineDispatchers.computation) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(coroutineDispatchers.main) {
|
||||||
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
|
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
|
||||||
}
|
}
|
||||||
// Decrypting by chunk of 500 keys in parallel
|
// Decrypting by chunk of 500 keys in parallel
|
||||||
@ -513,7 +512,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
.awaitAll()
|
.awaitAll()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(coroutineDispatchers.main) {
|
||||||
val stepProgress = StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size)
|
val stepProgress = StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size)
|
||||||
stepProgressListener?.onStepProgress(stepProgress)
|
stepProgressListener?.onStepProgress(stepProgress)
|
||||||
}
|
}
|
||||||
@ -532,7 +531,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
val progressListener = if (stepProgressListener != null) {
|
val progressListener = if (stepProgressListener != null) {
|
||||||
object : ProgressListener {
|
object : ProgressListener {
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
override fun onProgress(progress: Int, total: Int) {
|
||||||
cryptoCoroutineScope.launch(Dispatchers.Main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val stepProgress = StepProgressListener.Step.ImportingKey(progress, total)
|
val stepProgress = StepProgressListener.Step.ImportingKey(progress, total)
|
||||||
stepProgressListener.onStepProgress(stepProgress)
|
stepProgressListener.onStepProgress(stepProgress)
|
||||||
}
|
}
|
||||||
@ -581,16 +580,12 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getVersion(version: String): KeysVersionResult? {
|
override suspend fun getVersion(version: String): KeysVersionResult? {
|
||||||
return withContext(coroutineDispatchers.io) {
|
return sender.getKeyBackupVersion(version)
|
||||||
sender.getKeyBackupVersion(version)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
override suspend fun getCurrentVersion(): KeysVersionResult? {
|
override suspend fun getCurrentVersion(): KeysVersionResult? {
|
||||||
return withContext(coroutineDispatchers.io) {
|
return sender.getKeyBackupVersion()
|
||||||
sender.getKeyBackupVersion()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun forceUsingLastVersion(): Boolean {
|
override suspend fun forceUsingLastVersion(): Boolean {
|
||||||
@ -646,18 +641,16 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
Timber.w("checkAndStartKeysBackup: invalid state: $state")
|
Timber.w("checkAndStartKeysBackup: invalid state: $state")
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
keysBackupVersion = null
|
keysBackupVersion = null
|
||||||
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
|
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
|
||||||
|
try {
|
||||||
withContext(coroutineDispatchers.io) {
|
val data = getCurrentVersion()
|
||||||
try {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
val data = getCurrentVersion()
|
checkAndStartWithKeysBackupVersion(data)
|
||||||
withContext(coroutineDispatchers.crypto) {
|
}
|
||||||
checkAndStartWithKeysBackupVersion(data)
|
} catch (failure: Throwable) {
|
||||||
}
|
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
||||||
} catch (failure: Throwable) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
|
||||||
keysBackupStateManager.state = KeysBackupState.Unknown
|
keysBackupStateManager.state = KeysBackupState.Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -725,7 +718,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
|
override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
|
||||||
val info = olmMachine.getBackupKeys() ?: return null
|
val info = olmMachine.getBackupKeys() ?: return null
|
||||||
return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
|
return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
|
||||||
}
|
}
|
||||||
@ -878,7 +871,7 @@ internal class RustKeyBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
if (failure is Failure.ServerError) {
|
if (failure is Failure.ServerError) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(coroutineDispatchers.main) {
|
||||||
Timber.e(failure, "backupKeys: backupKeys failed.")
|
Timber.e(failure, "backupKeys: backupKeys failed.")
|
||||||
|
|
||||||
when (failure.error.code) {
|
when (failure.error.code) {
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,8 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto.network
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
@ -46,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||||
@ -56,6 +58,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
|
|||||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uniffi.olm.OutgoingVerificationRequest
|
import uniffi.olm.OutgoingVerificationRequest
|
||||||
import uniffi.olm.Request
|
import uniffi.olm.Request
|
||||||
@ -80,6 +83,7 @@ internal class RequestSender @Inject constructor(
|
|||||||
private val getSessionsDataTask: GetSessionsDataTask,
|
private val getSessionsDataTask: GetSessionsDataTask,
|
||||||
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
|
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
|
||||||
private val getRoomSessionDataTask: GetRoomSessionDataTask,
|
private val getRoomSessionDataTask: GetRoomSessionDataTask,
|
||||||
|
private val moshi: Moshi
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST_RETRY_COUNT = 3
|
const val REQUEST_RETRY_COUNT = 3
|
||||||
@ -97,16 +101,16 @@ internal class RequestSender @Inject constructor(
|
|||||||
suspend fun queryKeys(request: Request.KeysQuery): String {
|
suspend fun queryKeys(request: Request.KeysQuery): String {
|
||||||
val params = DownloadKeysForUsersTask.Params(request.users, null)
|
val params = DownloadKeysForUsersTask.Params(request.users, null)
|
||||||
val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(KeysQueryResponse::class.java)
|
val adapter = moshi.adapter(KeysQueryResponse::class.java)
|
||||||
return adapter.toJson(response)!!
|
return adapter.toJson(response)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadKeys(request: Request.KeysUpload): String {
|
suspend fun uploadKeys(request: Request.KeysUpload): String {
|
||||||
val body = MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java).fromJson(request.body)!!
|
val body = moshi.adapter<JsonDict>(Map::class.java).fromJson(request.body)!!
|
||||||
val params = UploadKeysTask.Params(body)
|
val params = UploadKeysTask.Params(body)
|
||||||
|
|
||||||
val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(KeysUploadResponse::class.java)
|
val adapter = moshi.adapter(KeysUploadResponse::class.java)
|
||||||
|
|
||||||
return adapter.toJson(response)!!
|
return adapter.toJson(response)!!
|
||||||
}
|
}
|
||||||
@ -118,42 +122,46 @@ internal class RequestSender @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): String {
|
private suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): SendResponse {
|
||||||
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRoomMessage(request: Request.RoomMessage): String {
|
suspend fun sendRoomMessage(request: Request.RoomMessage): String {
|
||||||
return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
val sendResponse = sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId)
|
||||||
|
val responseAdapter = moshi.adapter(SendResponse::class.java)
|
||||||
|
return responseAdapter.toJson(sendResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): String {
|
suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): SendResponse {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
val paramsAdapter = moshi.adapter<Content>(Map::class.java)
|
||||||
val jsonContent = adapter.fromJson(content)
|
val jsonContent = paramsAdapter.fromJson(content)
|
||||||
val event = Event(eventType, transactionId, jsonContent, roomId = roomId)
|
val event = Event(eventType, transactionId, jsonContent, roomId = roomId)
|
||||||
val params = SendVerificationMessageTask.Params(event)
|
val params = SendVerificationMessageTask.Params(event)
|
||||||
return this.sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT)
|
return sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendSignatureUpload(request: Request.SignatureUpload) {
|
suspend fun sendSignatureUpload(request: Request.SignatureUpload): String {
|
||||||
sendSignatureUpload(request.body)
|
return sendSignatureUpload(request.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendSignatureUpload(request: SignatureUploadRequest) {
|
suspend fun sendSignatureUpload(request: SignatureUploadRequest): String {
|
||||||
sendSignatureUpload(request.body)
|
return sendSignatureUpload(request.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendSignatureUpload(body: String) {
|
private suspend fun sendSignatureUpload(body: String): String {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<Map<String, Map<String, Any>>>(Map::class.java)
|
val paramsAdapter = moshi.adapter<Map<String, Map<String, Any>>>(Map::class.java)
|
||||||
val signatures = adapter.fromJson(body)!!
|
val signatures = paramsAdapter.fromJson(body)!!
|
||||||
val params = UploadSignaturesTask.Params(signatures)
|
val params = UploadSignaturesTask.Params(signatures)
|
||||||
this.signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
val response = signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||||
|
val responseAdapter = moshi.adapter(SignatureUploadResponse::class.java)
|
||||||
|
return responseAdapter.toJson(response)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadCrossSigningKeys(
|
suspend fun uploadCrossSigningKeys(
|
||||||
request: UploadSigningKeysRequest,
|
request: UploadSigningKeysRequest,
|
||||||
interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
|
interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
|
||||||
) {
|
) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
|
val adapter = moshi.adapter(RestKeyInfo::class.java)
|
||||||
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
|
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
|
||||||
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
|
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
|
||||||
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
|
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
|
||||||
@ -195,8 +203,7 @@ internal class RequestSender @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendToDevice(eventType: String, body: String, transactionId: String) {
|
suspend fun sendToDevice(eventType: String, body: String, transactionId: String) {
|
||||||
val adapter = MoshiProvider
|
val adapter = moshi
|
||||||
.providesMoshi()
|
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||||
.build()
|
.build()
|
||||||
@ -252,7 +259,7 @@ internal class RequestSender @Inject constructor(
|
|||||||
val keys = adapter.fromJson(request.rooms)!!
|
val keys = adapter.fromJson(request.rooms)!!
|
||||||
val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys))
|
val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys))
|
||||||
val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
|
||||||
val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java)
|
val responseAdapter = moshi.adapter(BackupKeysResult::class.java)
|
||||||
return responseAdapter.toJson(response)!!
|
return responseAdapter.toJson(response)!!
|
||||||
}
|
}
|
||||||
|
|
@ -22,18 +22,14 @@ import org.matrix.android.sdk.api.session.events.model.toContent
|
|||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
|
internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
|
||||||
data class Params(val roomId: String,
|
data class Params(val roomId: String,
|
||||||
val event: Event,
|
val event: Event
|
||||||
/**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
|
|
||||||
val keepKeys: List<String>? = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,52 +47,34 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||||||
|
|
||||||
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)
|
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)
|
||||||
|
|
||||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
|
||||||
params.keepKeys?.forEach {
|
|
||||||
localMutableContent.remove(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// let it throws
|
// let it throws
|
||||||
awaitCallback<MXEncryptEventContentResult> {
|
val result = cryptoService.encryptEventContent(localEvent.content ?: emptyMap(), localEvent.type, params.roomId)
|
||||||
cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
|
// Better handling of local echo, to avoid decrypting transition on remote echo
|
||||||
}.let { result ->
|
// Should I only do it for text messages?
|
||||||
val modifiedContent = HashMap(result.eventContent)
|
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||||
params.keepKeys?.forEach { toKeep ->
|
MXEventDecryptionResult(
|
||||||
localEvent.content?.get(toKeep)?.let {
|
clearEvent = Event(
|
||||||
// put it back in the encrypted thing
|
type = localEvent.type,
|
||||||
modifiedContent[toKeep] = it
|
content = localEvent.content,
|
||||||
}
|
roomId = localEvent.roomId
|
||||||
}
|
).toContent(),
|
||||||
val safeResult = result.copy(eventContent = modifiedContent)
|
forwardingCurve25519KeyChain = emptyList(),
|
||||||
// Better handling of local echo, to avoid decrypting transition on remote echo
|
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||||
// Should I only do it for text messages?
|
claimedEd25519Key = cryptoService.getMyCryptoDevice().fingerprint()
|
||||||
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
|
||||||
MXEventDecryptionResult(
|
|
||||||
clearEvent = Event(
|
|
||||||
type = localEvent.type,
|
|
||||||
content = localEvent.content,
|
|
||||||
roomId = localEvent.roomId
|
|
||||||
).toContent(),
|
|
||||||
forwardingCurve25519KeyChain = emptyList(),
|
|
||||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
|
||||||
claimedEd25519Key = cryptoService.getMyDevice().fingerprint()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
|
||||||
localEcho.type = EventType.ENCRYPTED
|
|
||||||
localEcho.content = ContentMapper.map(modifiedContent)
|
|
||||||
decryptionLocalEcho?.also {
|
|
||||||
localEcho.setDecryptionResult(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return localEvent.copy(
|
|
||||||
type = safeResult.eventType,
|
|
||||||
content = safeResult.eventContent
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
||||||
|
localEcho.type = EventType.ENCRYPTED
|
||||||
|
localEcho.content = ContentMapper.map(result.eventContent)
|
||||||
|
decryptionLocalEcho?.also {
|
||||||
|
localEcho.setDecryptionResult(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localEvent.copy(
|
||||||
|
type = result.eventType,
|
||||||
|
content = result.eventContent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,7 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||||||
if (params.encrypt && !params.event.isEncrypted()) {
|
if (params.encrypt && !params.event.isEncrypted()) {
|
||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.event.roomId ?: "",
|
params.event.roomId ?: "",
|
||||||
params.event,
|
params.event
|
||||||
listOf("m.relates_to")
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return params.event
|
return params.event
|
||||||
|
@ -22,11 +22,12 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
|||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.toMatrixErrorStr
|
import org.matrix.android.sdk.internal.util.toMatrixErrorStr
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, String> {
|
internal interface SendVerificationMessageTask : Task<SendVerificationMessageTask.Params, SendResponse> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val event: Event
|
val event: Event
|
||||||
)
|
)
|
||||||
@ -39,10 +40,9 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
|
private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
|
||||||
|
|
||||||
override suspend fun execute(params: SendVerificationMessageTask.Params): String {
|
override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse {
|
||||||
val event = handleEncryption(params)
|
val event = handleEncryption(params)
|
||||||
val localId = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
|
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
|
||||||
val response = executeRequest(globalErrorReceiver) {
|
val response = executeRequest(globalErrorReceiver) {
|
||||||
@ -54,7 +54,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
|
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
|
||||||
return response.eventId
|
return response
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr())
|
localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr())
|
||||||
throw e
|
throw e
|
||||||
@ -67,7 +67,6 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.event.roomId ?: "",
|
params.event.roomId ?: "",
|
||||||
params.event,
|
params.event,
|
||||||
listOf("m.relates_to")
|
|
||||||
))
|
))
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
// We said it's ok to send verification request in clear
|
// We said it's ok to send verification request in clear
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
|
||||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, Unit> {
|
internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, SignatureUploadResponse> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val signatures: Map<String, Map<String, Any>>
|
val signatures: Map<String, Map<String, Any>>
|
||||||
)
|
)
|
||||||
@ -33,21 +33,13 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
|
|||||||
private val globalErrorReceiver: GlobalErrorReceiver
|
private val globalErrorReceiver: GlobalErrorReceiver
|
||||||
) : UploadSignaturesTask {
|
) : UploadSignaturesTask {
|
||||||
|
|
||||||
override suspend fun execute(params: UploadSignaturesTask.Params) {
|
override suspend fun execute(params: UploadSignaturesTask.Params): SignatureUploadResponse {
|
||||||
try {
|
return executeRequest(
|
||||||
val response = executeRequest(
|
globalErrorReceiver,
|
||||||
globalErrorReceiver,
|
canRetry = true,
|
||||||
canRetry = true,
|
maxRetriesCount = 10
|
||||||
maxRetriesCount = 10
|
) {
|
||||||
) {
|
cryptoApi.uploadSignatures(params.signatures)
|
||||||
cryptoApi.uploadSignatures(params.signatures)
|
|
||||||
}
|
|
||||||
if (response.failures?.isNotEmpty() == true) {
|
|
||||||
throw Throwable(response.failures.toString())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} catch (f: Failure) {
|
|
||||||
throw f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.verification
|
|||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
@ -26,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
|
|||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
|
import org.matrix.android.sdk.internal.crypto.OlmMachineProvider
|
||||||
@ -77,31 +77,41 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
olmMachineProvider.olmMachine
|
olmMachineProvider.olmMachine
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
|
private val dispatcher = UpdateDispatcher(olmMachine.verificationListeners)
|
||||||
|
|
||||||
/** The main entry point for the verification service
|
/**
|
||||||
*
|
*
|
||||||
* All verification related events should be forwarded through this method to
|
* All verification related events should be forwarded through this method to
|
||||||
* the verification service.
|
* the verification service.
|
||||||
*
|
*
|
||||||
* Since events are at this point already handled by the rust-sdk through the receival
|
* If the verification event is not encrypted it should be provided to the olmMachine.
|
||||||
* of the to-device events and the decryption of room events, this method mainly just
|
* Otherwise events are at this point already handled by the rust-sdk through the receival
|
||||||
|
* of the to-device events and the decryption of room events. In this case this method mainly just
|
||||||
* fetches the appropriate rust object that will be created or updated by the event and
|
* fetches the appropriate rust object that will be created or updated by the event and
|
||||||
* dispatches updates to our listeners.
|
* dispatches updates to our listeners.
|
||||||
*/
|
*/
|
||||||
internal suspend fun onEvent(event: Event) = when (event.getClearType()) {
|
internal suspend fun onEvent(roomId: String?, event: Event) {
|
||||||
// I'm not entirely sure why getClearType() returns a msgtype in one case
|
if (roomId != null && !event.isEncrypted()) {
|
||||||
// and a event type in the other case, but this is how the old verification
|
olmMachine.receiveUnencryptedVerificationEvent(roomId, event)
|
||||||
// service did things and it does seem to work.
|
}
|
||||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> onRequest(event)
|
when (event.getClearType()) {
|
||||||
EventType.KEY_VERIFICATION_START -> onStart(event)
|
EventType.KEY_VERIFICATION_REQUEST -> onRequest(event, fromRoomMessage = false)
|
||||||
EventType.KEY_VERIFICATION_READY,
|
EventType.KEY_VERIFICATION_START -> onStart(event)
|
||||||
EventType.KEY_VERIFICATION_ACCEPT,
|
EventType.KEY_VERIFICATION_READY,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_ACCEPT,
|
||||||
EventType.KEY_VERIFICATION_MAC,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_MAC,
|
||||||
EventType.KEY_VERIFICATION_DONE -> onUpdate(event)
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
else -> {
|
EventType.KEY_VERIFICATION_DONE -> onUpdate(event)
|
||||||
|
EventType.MESSAGE -> onRoomMessage(event)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRoomMessage(event: Event) {
|
||||||
|
val messageContent = event.getClearContent()?.toModel<MessageContent>() ?: return
|
||||||
|
if (messageContent.msgType == MessageType.MSGTYPE_VERIFICATION_REQUEST) {
|
||||||
|
onRequest(event, fromRoomMessage = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,9 +120,9 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
val sender = event.senderId ?: return
|
val sender = event.senderId ?: return
|
||||||
val flowId = getFlowId(event) ?: return
|
val flowId = getFlowId(event) ?: return
|
||||||
|
|
||||||
this.olmMachine.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated()
|
olmMachine.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated()
|
||||||
val verification = this.getExistingTransaction(sender, flowId) ?: return
|
val verification = getExistingTransaction(sender, flowId) ?: return
|
||||||
this.dispatcher.dispatchTxUpdated(verification)
|
dispatcher.dispatchTxUpdated(verification)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if the start event created new verification objects and dispatch updates */
|
/** Check if the start event created new verification objects and dispatch updates */
|
||||||
@ -120,8 +130,8 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
val sender = event.senderId ?: return
|
val sender = event.senderId ?: return
|
||||||
val flowId = getFlowId(event) ?: return
|
val flowId = getFlowId(event) ?: return
|
||||||
|
|
||||||
val verification = this.getExistingTransaction(sender, flowId) ?: return
|
val verification = getExistingTransaction(sender, flowId) ?: return
|
||||||
val request = this.olmMachine.getVerificationRequest(sender, flowId)
|
val request = olmMachine.getVerificationRequest(sender, flowId)
|
||||||
|
|
||||||
if (request != null && request.isReady()) {
|
if (request != null && request.isReady()) {
|
||||||
// If this is a SAS verification originating from a `m.key.verification.request`
|
// If this is a SAS verification originating from a `m.key.verification.request`
|
||||||
@ -132,59 +142,54 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
Timber.d("## Verification: Auto accepting SAS verification with $sender")
|
Timber.d("## Verification: Auto accepting SAS verification with $sender")
|
||||||
verification.accept()
|
verification.accept()
|
||||||
} else {
|
} else {
|
||||||
this.dispatcher.dispatchTxUpdated(verification)
|
dispatcher.dispatchTxUpdated(verification)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This didn't originate from a request, so tell our listeners that
|
// This didn't originate from a request, so tell our listeners that
|
||||||
// this is a new verification.
|
// this is a new verification.
|
||||||
this.dispatcher.dispatchTxAdded(verification)
|
dispatcher.dispatchTxAdded(verification)
|
||||||
// The IncomingVerificationRequestHandler seems to only listen to updates
|
// The IncomingVerificationRequestHandler seems to only listen to updates
|
||||||
// so let's trigger an update after the addition as well.
|
// so let's trigger an update after the addition as well.
|
||||||
this.dispatcher.dispatchTxUpdated(verification)
|
dispatcher.dispatchTxUpdated(verification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if the request event created a nev verification request object and dispatch that it dis so */
|
/** Check if the request event created a nev verification request object and dispatch that it dis so */
|
||||||
private fun onRequest(event: Event) {
|
private fun onRequest(event: Event, fromRoomMessage: Boolean) {
|
||||||
val flowId = getFlowId(event) ?: return
|
val flowId = if (fromRoomMessage) {
|
||||||
|
event.eventId
|
||||||
|
} else {
|
||||||
|
event.getClearContent().toModel<ToDeviceVerificationEvent>()?.transactionId
|
||||||
|
} ?: return
|
||||||
val sender = event.senderId ?: return
|
val sender = event.senderId ?: return
|
||||||
|
val request = getExistingVerificationRequest(sender, flowId) ?: return
|
||||||
|
|
||||||
val request = this.getExistingVerificationRequest(sender, flowId) ?: return
|
dispatcher.dispatchRequestAdded(request)
|
||||||
|
|
||||||
this.dispatcher.dispatchRequestAdded(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addListener(listener: VerificationService.Listener) {
|
override fun addListener(listener: VerificationService.Listener) {
|
||||||
this.dispatcher.addListener(listener)
|
dispatcher.addListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeListener(listener: VerificationService.Listener) {
|
override fun removeListener(listener: VerificationService.Listener) {
|
||||||
this.dispatcher.removeListener(listener)
|
dispatcher.removeListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||||
// TODO this doesn't seem to be used anymore?
|
olmMachine.getDevice(userId, deviceID)?.markAsTrusted()
|
||||||
runBlocking {
|
|
||||||
val device = olmMachine.getDevice(userId, deviceID)
|
|
||||||
device?.markAsTrusted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) {
|
|
||||||
// TODO This should be handled inside the rust-sdk decryption method
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExistingTransaction(
|
override fun getExistingTransaction(
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
tid: String,
|
tid: String,
|
||||||
): VerificationTransaction? {
|
): VerificationTransaction? {
|
||||||
return this.olmMachine.getVerification(otherUserId, tid)
|
return olmMachine.getVerification(otherUserId, tid)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExistingVerificationRequests(
|
override fun getExistingVerificationRequests(
|
||||||
otherUserId: String
|
otherUserId: String
|
||||||
): List<PendingVerificationRequest> {
|
): List<PendingVerificationRequest> {
|
||||||
return this.olmMachine.getVerificationRequests(otherUserId).map {
|
return olmMachine.getVerificationRequests(otherUserId).map {
|
||||||
it.toPendingVerificationRequest()
|
it.toPendingVerificationRequest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,7 +199,7 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
tid: String?
|
tid: String?
|
||||||
): PendingVerificationRequest? {
|
): PendingVerificationRequest? {
|
||||||
return if (tid != null) {
|
return if (tid != null) {
|
||||||
this.olmMachine.getVerificationRequest(otherUserId, tid)?.toPendingVerificationRequest()
|
olmMachine.getVerificationRequest(otherUserId, tid)?.toPendingVerificationRequest()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -224,29 +229,24 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestKeyVerification(
|
override suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest {
|
||||||
methods: List<VerificationMethod>,
|
val verification = when (val identity = olmMachine.getIdentity(olmMachine.userId())) {
|
||||||
otherUserId: String,
|
is OwnUserIdentity -> identity.requestVerification(methods)
|
||||||
otherDevices: List<String>?
|
|
||||||
): PendingVerificationRequest {
|
|
||||||
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
|
||||||
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
|
|
||||||
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
|
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
|
||||||
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
|
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
|
||||||
}
|
}
|
||||||
|
|
||||||
return verification.toPendingVerificationRequest()
|
return verification.toPendingVerificationRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestKeyVerificationInDMs(
|
override suspend fun requestKeyVerificationInDMs(
|
||||||
methods: List<VerificationMethod>,
|
methods: List<VerificationMethod>,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
localId: String?
|
localId: String?
|
||||||
): PendingVerificationRequest {
|
): PendingVerificationRequest {
|
||||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
olmMachine.ensureUsersKeys(listOf(otherUserId))
|
||||||
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
|
||||||
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
|
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
|
||||||
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
|
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
|
||||||
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
|
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
|
||||||
}
|
}
|
||||||
@ -254,21 +254,20 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
return verification.toPendingVerificationRequest()
|
return verification.toPendingVerificationRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readyPendingVerification(
|
override suspend fun readyPendingVerification(
|
||||||
methods: List<VerificationMethod>,
|
methods: List<VerificationMethod>,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
transactionId: String
|
transactionId: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
val request = olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||||
|
|
||||||
return if (request != null) {
|
return if (request != null) {
|
||||||
runBlocking { request.acceptWithMethods(methods) }
|
request.acceptWithMethods(methods)
|
||||||
|
|
||||||
if (request.isReady()) {
|
if (request.isReady()) {
|
||||||
val qrcode = request.startQrVerification()
|
val qrcode = request.startQrVerification()
|
||||||
|
|
||||||
if (qrcode != null) {
|
if (qrcode != null) {
|
||||||
this.dispatcher.dispatchTxAdded(qrcode)
|
dispatcher.dispatchTxAdded(qrcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
@ -280,82 +279,50 @@ internal class RustVerificationService @Inject constructor(private val olmMachin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readyPendingVerificationInDMs(
|
override suspend fun beginKeyVerification(
|
||||||
methods: List<VerificationMethod>,
|
|
||||||
otherUserId: String,
|
|
||||||
roomId: String,
|
|
||||||
transactionId: String
|
|
||||||
): Boolean {
|
|
||||||
return readyPendingVerification(methods, otherUserId, transactionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun beginKeyVerification(
|
|
||||||
method: VerificationMethod,
|
method: VerificationMethod,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
transactionId: String
|
||||||
transactionId: String?
|
|
||||||
): String? {
|
): String? {
|
||||||
return if (method == VerificationMethod.SAS) {
|
return if (method == VerificationMethod.SAS) {
|
||||||
if (transactionId != null) {
|
val request = olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||||
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
|
||||||
|
|
||||||
runBlocking {
|
val sas = request?.startSasVerification()
|
||||||
val sas = request?.startSasVerification()
|
|
||||||
|
|
||||||
if (sas != null) {
|
if (sas != null) {
|
||||||
dispatcher.dispatchTxAdded(sas)
|
dispatcher.dispatchTxAdded(sas)
|
||||||
sas.transactionId
|
sas.transactionId
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// This starts the short SAS flow, the one that doesn't start with
|
null
|
||||||
// a `m.key.verification.request`, Element web stopped doing this, might
|
|
||||||
// be wise do do so as well
|
|
||||||
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
|
|
||||||
// triggers this is called `manuallyVerify()`
|
|
||||||
runBlocking {
|
|
||||||
val verification = olmMachine.getDevice(otherUserId, otherDeviceId)?.startVerification()
|
|
||||||
if (verification != null) {
|
|
||||||
dispatcher.dispatchTxAdded(verification)
|
|
||||||
verification.transactionId
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Unknown verification method")
|
throw IllegalArgumentException("Unknown verification method")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beginKeyVerificationInDMs(
|
override suspend fun beginDeviceVerification(otherUserId: String, otherDeviceId: String): String? {
|
||||||
method: VerificationMethod,
|
// This starts the short SAS flow, the one that doesn't start with
|
||||||
transactionId: String,
|
// a `m.key.verification.request`, Element web stopped doing this, might
|
||||||
roomId: String,
|
// be wise do do so as well
|
||||||
otherUserId: String,
|
// DeviceListBottomSheetViewModel triggers this, interestingly the method that
|
||||||
otherDeviceId: String
|
// triggers this is called `manuallyVerify()`
|
||||||
): String {
|
val otherDevice = olmMachine.getDevice(otherUserId, otherDeviceId)
|
||||||
beginKeyVerification(method, otherUserId, otherDeviceId, transactionId)
|
val verification = otherDevice?.startVerification()
|
||||||
// TODO what's the point of returning the same ID we got as an argument?
|
return if (verification != null) {
|
||||||
// We do this because the old verification service did so
|
dispatcher.dispatchTxAdded(verification)
|
||||||
return transactionId
|
verification.transactionId
|
||||||
}
|
} else {
|
||||||
|
null
|
||||||
override fun cancelVerificationRequest(request: PendingVerificationRequest) {
|
|
||||||
val verificationRequest = request.transactionId?.let {
|
|
||||||
this.olmMachine.getVerificationRequest(request.otherUserId, it)
|
|
||||||
}
|
}
|
||||||
runBlocking { verificationRequest?.cancel() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun declineVerificationRequestInDMs(
|
override suspend fun cancelVerificationRequest(request: PendingVerificationRequest) {
|
||||||
otherUserId: String,
|
request.transactionId ?: return
|
||||||
transactionId: String,
|
cancelVerificationRequest(request.otherUserId, request.transactionId)
|
||||||
roomId: String
|
}
|
||||||
) {
|
|
||||||
val verificationRequest = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
override suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String) {
|
||||||
runBlocking { verificationRequest?.cancel() }
|
val verificationRequest = olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||||
|
verificationRequest?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
assert(!isOpen)
|
||||||
isOpen = true
|
isOpen = true
|
||||||
globalErrorHandler.listener = this
|
globalErrorHandler.listener = this
|
||||||
cryptoService.get().ensureDevice()
|
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach {
|
lifecycleObservers.forEach {
|
||||||
it.onSessionStarted(this)
|
it.onSessionStarted(this)
|
||||||
|
@ -49,7 +49,6 @@ import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
|||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.session.search.SearchTask
|
import org.matrix.android.sdk.internal.session.search.SearchTask
|
||||||
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
|
|
||||||
internal class DefaultRoom(override val roomId: String,
|
internal class DefaultRoom(override val roomId: String,
|
||||||
@ -117,9 +116,7 @@ internal class DefaultRoom(override val roomId: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun prepareToEncrypt() {
|
override suspend fun prepareToEncrypt() {
|
||||||
awaitCallback<Unit> {
|
cryptoService.prepareToEncrypt(roomId)
|
||||||
cryptoService.prepareToEncrypt(roomId, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun enableEncryption(algorithm: String, force: Boolean) {
|
override suspend fun enableEncryption(algorithm: String, force: Boolean) {
|
||||||
|
@ -156,7 +156,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
|
|||||||
* Invoke the event decryption mechanism for a specific event
|
* Invoke the event decryption mechanism for a specific event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
private suspend fun decryptIfNeeded(event: Event, roomId: String) {
|
||||||
try {
|
try {
|
||||||
// Event from sync does not have roomId, so add it to the event first
|
// Event from sync does not have roomId, so add it to the event first
|
||||||
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
||||||
|
@ -43,6 +43,7 @@ internal class RealmSendingEventsDataSource(
|
|||||||
private var roomEntity: RoomEntity? = null
|
private var roomEntity: RoomEntity? = null
|
||||||
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
||||||
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
||||||
|
private val builtEvents = ArrayList<TimelineEvent>()
|
||||||
|
|
||||||
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
|
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
|
||||||
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
||||||
|
@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
|
|||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
@ -126,7 +127,9 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val result = cryptoService.decryptEvent(request.event, timelineId)
|
val result = runBlocking {
|
||||||
|
cryptoService.decryptEvent(request.event, timelineId)
|
||||||
|
}
|
||||||
Timber.v("Successfully decrypted event ${event.eventId}")
|
Timber.v("Successfully decrypted event ${event.eventId}")
|
||||||
realm.executeTransaction {
|
realm.executeTransaction {
|
||||||
val eventId = event.eventId ?: return@executeTransaction
|
val eventId = event.eventId ?: return@executeTransaction
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
|
|||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
@ -343,7 +344,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||||||
return roomEntity
|
return roomEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleTimelineEvents(realm: Realm,
|
private fun handleTimelineEvents(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
roomEntity: RoomEntity,
|
roomEntity: RoomEntity,
|
||||||
eventList: List<Event>,
|
eventList: List<Event>,
|
||||||
@ -458,7 +459,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||||||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
private fun decryptIfNeeded(event: Event, roomId: String) {
|
||||||
try {
|
try {
|
||||||
// Event from sync does not have roomId, so add it to the event first
|
// Event from sync does not have roomId, so add it to the event first
|
||||||
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
val result = runBlocking {
|
||||||
|
cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
||||||
|
}
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
payload = result.clearEvent,
|
payload = result.clearEvent,
|
||||||
senderKey = result.senderCurve25519Key,
|
senderKey = result.senderCurve25519Key,
|
||||||
|
@ -134,10 +134,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
|
|||||||
onView(withId(R.id.bottomSheetFragmentContainer))
|
onView(withId(R.id.bottomSheetFragmentContainer))
|
||||||
.check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
|
.check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
|
||||||
|
|
||||||
val request = existingSession!!.cryptoService().verificationService().requestKeyVerification(
|
val request = existingSession!!.cryptoService().verificationService().requestSelfKeyVerification(
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
|
||||||
existingSession!!.myUserId,
|
|
||||||
listOf(uiSession.sessionParams.deviceId!!)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val transactionId = request.transactionId!!
|
val transactionId = request.transactionId!!
|
||||||
|
@ -64,12 +64,12 @@ fun Session.startSyncing(context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Tell is the session has unsaved e2e keys in the backup
|
* Tell is the session has unsaved e2e keys in the backup
|
||||||
*/
|
*/
|
||||||
fun Session.hasUnsavedKeys(): Boolean {
|
suspend fun Session.hasUnsavedKeys(): Boolean {
|
||||||
return cryptoService().inboundGroupSessionsCount(false) > 0 &&
|
return cryptoService().inboundGroupSessionsCount(false) > 0 &&
|
||||||
cryptoService().keysBackupService().state != KeysBackupState.ReadyToBackUp
|
cryptoService().keysBackupService().state != KeysBackupState.ReadyToBackUp
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Session.cannotLogoutSafely(): Boolean {
|
suspend fun Session.cannotLogoutSafely(): Boolean {
|
||||||
// has some encrypted chat
|
// has some encrypted chat
|
||||||
return hasUnsavedKeys() ||
|
return hasUnsavedKeys() ||
|
||||||
// has local cross signing keys
|
// has local cross signing keys
|
||||||
|
@ -26,5 +26,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
|
|||||||
data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async<KeysBackupVersionTrust> = Uninitialized,
|
data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async<KeysBackupVersionTrust> = Uninitialized,
|
||||||
val keysBackupState: KeysBackupState? = null,
|
val keysBackupState: KeysBackupState? = null,
|
||||||
val keysBackupVersion: KeysVersionResult? = null,
|
val keysBackupVersion: KeysVersionResult? = null,
|
||||||
|
val remainingKeysToBackup: Int = 0,
|
||||||
val deleteBackupRequest: Async<Unit> = Uninitialized) :
|
val deleteBackupRequest: Async<Unit> = Uninitialized) :
|
||||||
MavericksState
|
MavericksState
|
||||||
|
@ -124,10 +124,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
|
|||||||
style(ItemStyle.BIG_TEXT)
|
style(ItemStyle.BIG_TEXT)
|
||||||
hasIndeterminateProcess(true)
|
hasIndeterminateProcess(true)
|
||||||
|
|
||||||
val totalKeys = host.session.cryptoService().inboundGroupSessionsCount(false)
|
val remainingKeysToBackup = data.remainingKeysToBackup
|
||||||
val backedUpKeys = host.session.cryptoService().inboundGroupSessionsCount(true)
|
|
||||||
|
|
||||||
val remainingKeysToBackup = totalKeys - backedUpKeys
|
|
||||||
|
|
||||||
if (data.keysBackupVersionTrust()?.usable == false) {
|
if (data.keysBackupVersionTrust()?.usable == false) {
|
||||||
description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence())
|
description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence())
|
||||||
|
@ -18,7 +18,6 @@ package im.vector.app.features.crypto.keysbackup.settings
|
|||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
@ -32,7 +31,6 @@ import org.matrix.android.sdk.api.session.Session
|
|||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
||||||
session: Session
|
session: Session
|
||||||
@ -46,6 +44,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
|
|
||||||
companion object : MavericksViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> by hiltMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
private val cryptoService = session.cryptoService()
|
||||||
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
|
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -75,34 +74,12 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
|
|
||||||
private fun getKeysBackupTrust() = withState { state ->
|
private fun getKeysBackupTrust() = withState { state ->
|
||||||
val versionResult = keysBackupService.keysBackupVersion
|
val versionResult = keysBackupService.keysBackupVersion
|
||||||
Timber.d("BACKUP: HEEEEEEE $versionResult ${state.keysBackupVersionTrust}")
|
|
||||||
|
|
||||||
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
|
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
|
||||||
setState {
|
setState { copy(deleteBackupRequest = Uninitialized) }
|
||||||
copy(
|
suspend {
|
||||||
keysBackupVersionTrust = Loading(),
|
keysBackupService.getKeysBackupTrust(versionResult)
|
||||||
deleteBackupRequest = Uninitialized
|
}.execute {
|
||||||
)
|
copy(keysBackupVersionTrust = it)
|
||||||
}
|
|
||||||
Timber.d("BACKUP: HEEEEEEE TWO")
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
val data = keysBackupService.getKeysBackupTrust(versionResult)
|
|
||||||
Timber.d("BACKUP: HEEEE suceeeded $data")
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
keysBackupVersionTrust = Success(data)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
Timber.d("BACKUP: HEEEE FAILED $failure")
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
keysBackupVersionTrust = Fail(failure)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,10 +96,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
keysBackupVersion = keysBackupService.keysBackupVersion
|
keysBackupVersion = keysBackupService.keysBackupVersion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
when (newState) {
|
||||||
|
KeysBackupState.BackingUp, KeysBackupState.WillBackUp -> updateKeysCount()
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
getKeysBackupTrust()
|
getKeysBackupTrust()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateKeysCount() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val totalKeys = cryptoService.inboundGroupSessionsCount(false)
|
||||||
|
val backedUpKeys = cryptoService.inboundGroupSessionsCount(true)
|
||||||
|
val remainingKeysToBackup = totalKeys - backedUpKeys
|
||||||
|
setState {
|
||||||
|
copy(remainingKeysToBackup = remainingKeysToBackup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun deleteCurrentBackup() {
|
private fun deleteCurrentBackup() {
|
||||||
val keysBackupService = keysBackupService
|
val keysBackupService = keysBackupService
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ class KeyRequestHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (deviceInfo.isUnknown) {
|
if (deviceInfo.isUnknown) {
|
||||||
session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false), userId, deviceId)
|
session?.cryptoService()?.verificationService()?.markedLocallyAsManuallyVerified(userId, deviceId)
|
||||||
|
|
||||||
deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||||
|
|
||||||
|
@ -149,10 +149,12 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
// as we are going to reset, we'd better cancel all outgoing requests
|
// as we are going to reset, we'd better cancel all outgoing requests
|
||||||
// if not they could be accepted in the middle of the reset process
|
// if not they could be accepted in the middle of the reset process
|
||||||
// and cause strange use cases
|
// and cause strange use cases
|
||||||
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
|
viewModelScope.launch {
|
||||||
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach {
|
||||||
|
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
||||||
|
}
|
||||||
|
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
|
||||||
}
|
}
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResetAll() {
|
private fun handleResetAll() {
|
||||||
|
@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
|
|||||||
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
|
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -91,12 +90,7 @@ class BootstrapCrossSigningTask @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
awaitCallback<Unit> {
|
crossSigningService.initializeCrossSigning(params.userInteractiveAuthInterceptor)
|
||||||
crossSigningService.initializeCrossSigning(
|
|
||||||
params.userInteractiveAuthInterceptor,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
|
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
|
||||||
return BootstrapResult.SuccessCrossSigningOnly
|
return BootstrapResult.SuccessCrossSigningOnly
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
|
|||||||
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
||||||
import im.vector.app.features.popup.PopupAlertManager
|
import im.vector.app.features.popup.PopupAlertManager
|
||||||
import im.vector.app.features.popup.VerificationVectorAlert
|
import im.vector.app.features.popup.VerificationVectorAlert
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
@ -42,7 +44,8 @@ import javax.inject.Singleton
|
|||||||
class IncomingVerificationRequestHandler @Inject constructor(
|
class IncomingVerificationRequestHandler @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private var avatarRenderer: Provider<AvatarRenderer>,
|
private var avatarRenderer: Provider<AvatarRenderer>,
|
||||||
private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
|
private val popupAlertManager: PopupAlertManager,
|
||||||
|
private val coroutineScope: CoroutineScope) : VerificationService.Listener {
|
||||||
|
|
||||||
private var session: Session? = null
|
private var session: Session? = null
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||||||
// TODO maybe check also if
|
// TODO maybe check also if
|
||||||
val uid = "kvr_${tx.transactionId}"
|
val uid = "kvr_${tx.transactionId}"
|
||||||
when (tx.state) {
|
when (tx.state) {
|
||||||
is VerificationTxState.OnStarted -> {
|
is VerificationTxState.OnStarted -> {
|
||||||
// Add a notification for every incoming request
|
// Add a notification for every incoming request
|
||||||
val user = session?.getUser(tx.otherUserId)
|
val user = session?.getUser(tx.otherUserId)
|
||||||
val name = user?.toMatrixItem()?.getBestName() ?: tx.otherUserId
|
val name = user?.toMatrixItem()?.getBestName() ?: tx.otherUserId
|
||||||
@ -88,12 +91,14 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||||||
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
|
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dismissedAction = Runnable {
|
dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
|
||||||
tx.cancel()
|
tx.cancel()
|
||||||
}
|
}
|
||||||
addButton(
|
addButton(
|
||||||
context.getString(R.string.action_ignore),
|
context.getString(R.string.action_ignore),
|
||||||
{ tx.cancel() }
|
LaunchCoroutineRunnable(coroutineScope) {
|
||||||
|
tx.cancel()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
addButton(
|
addButton(
|
||||||
context.getString(R.string.action_open),
|
context.getString(R.string.action_open),
|
||||||
@ -160,10 +165,9 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dismissedAction = Runnable {
|
dismissedAction = LaunchCoroutineRunnable(coroutineScope) {
|
||||||
session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
|
session?.cryptoService()?.verificationService()?.cancelVerificationRequest(pr.otherUserId,
|
||||||
pr.transactionId ?: "",
|
pr.transactionId ?: ""
|
||||||
pr.roomId ?: ""
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
colorAttribute = R.attr.vctr_notice_secondary
|
colorAttribute = R.attr.vctr_notice_secondary
|
||||||
@ -181,6 +185,14 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LaunchCoroutineRunnable(private val coroutineScope: CoroutineScope, private val block: suspend () -> Unit) : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
coroutineScope.launch {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
|
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
|
||||||
"verificationRequest_${pr.transactionId}"
|
"verificationRequest_${pr.transactionId}"
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
package im.vector.app.features.crypto.verification
|
package im.vector.app.features.crypto.verification
|
||||||
|
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.Fail
|
|
||||||
import com.airbnb.mvrx.Loading
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
@ -131,34 +129,37 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||||||
session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction
|
session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasAnyOtherSession = session.cryptoService()
|
viewModelScope.launch {
|
||||||
.getCryptoDeviceInfo(session.myUserId)
|
|
||||||
.any {
|
|
||||||
it.deviceId != session.sessionParams.deviceId
|
|
||||||
}
|
|
||||||
|
|
||||||
setState {
|
val hasAnyOtherSession = session.cryptoService()
|
||||||
copy(
|
.getCryptoDeviceInfoList(session.myUserId)
|
||||||
otherUserMxItem = userItem?.toMatrixItem(),
|
.any {
|
||||||
sasTransactionState = sasTx?.state,
|
it.deviceId != session.sessionParams.deviceId
|
||||||
qrTransactionState = qrTx?.state,
|
}
|
||||||
transactionId = pr?.transactionId ?: initialState.verificationId,
|
|
||||||
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
|
|
||||||
isMe = initialState.otherUserId == session.myUserId,
|
|
||||||
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
|
|
||||||
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
|
|
||||||
hasAnyOtherSession = hasAnyOtherSession
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoReady) {
|
setState {
|
||||||
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
copy(
|
||||||
session.cryptoService().verificationService()
|
otherUserMxItem = userItem?.toMatrixItem(),
|
||||||
.readyPendingVerification(
|
sasTransactionState = sasTx?.state,
|
||||||
supportedVerificationMethodsProvider.provide(),
|
qrTransactionState = qrTx?.state,
|
||||||
pr!!.otherUserId,
|
transactionId = pr?.transactionId ?: initialState.verificationId,
|
||||||
pr.transactionId ?: ""
|
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
|
||||||
)
|
isMe = initialState.otherUserId == session.myUserId,
|
||||||
|
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
|
||||||
|
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
|
||||||
|
hasAnyOtherSession = hasAnyOtherSession
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoReady) {
|
||||||
|
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
||||||
|
session.cryptoService().verificationService()
|
||||||
|
.readyPendingVerification(
|
||||||
|
supportedVerificationMethodsProvider.provide(),
|
||||||
|
pr!!.otherUserId,
|
||||||
|
pr.transactionId ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,14 +193,16 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
|
private fun cancelAllPendingVerifications(state: VerificationBottomSheetViewState) {
|
||||||
session.cryptoService()
|
viewModelScope.launch {
|
||||||
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
|
session.cryptoService()
|
||||||
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
.verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let {
|
||||||
}
|
session.cryptoService().verificationService().cancelVerificationRequest(it)
|
||||||
session.cryptoService()
|
}
|
||||||
.verificationService()
|
session.cryptoService()
|
||||||
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
|
.verificationService()
|
||||||
?.cancel(CancelCode.User)
|
.getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "")
|
||||||
|
?.cancel(CancelCode.User)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun continueFromCancel() {
|
fun continueFromCancel() {
|
||||||
@ -232,109 +235,25 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
is VerificationAction.RequestVerificationByDM -> {
|
is VerificationAction.RequestVerificationByDM -> {
|
||||||
if (roomId == null) {
|
handleRequestVerificationByDM(roomId, otherUserId)
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
pendingLocalId = localId,
|
|
||||||
pendingRequest = Loading()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
viewModelScope.launch {
|
|
||||||
val result = runCatching { session.createDirectRoom(otherUserId) }
|
|
||||||
result.fold(
|
|
||||||
{ data ->
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
roomId = data,
|
|
||||||
pendingRequest = Success(
|
|
||||||
session
|
|
||||||
.cryptoService()
|
|
||||||
.verificationService()
|
|
||||||
.requestKeyVerificationInDMs(
|
|
||||||
supportedVerificationMethodsProvider.provide(),
|
|
||||||
otherUserId,
|
|
||||||
data,
|
|
||||||
pendingLocalId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ failure ->
|
|
||||||
setState {
|
|
||||||
copy(pendingRequest = Fail(failure))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
pendingRequest = Success(session
|
|
||||||
.cryptoService()
|
|
||||||
.verificationService()
|
|
||||||
.requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
is VerificationAction.StartSASVerification -> {
|
is VerificationAction.StartSASVerification -> {
|
||||||
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
|
handleStartSASVerification(otherUserId, action)
|
||||||
?: return@withState
|
|
||||||
val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
|
|
||||||
if (roomId == null) {
|
|
||||||
session.cryptoService().verificationService().beginKeyVerification(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
otherUserId = request.otherUserId,
|
|
||||||
otherDeviceId = otherDevice ?: "",
|
|
||||||
transactionId = action.pendingRequestTransactionId
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
session.cryptoService().verificationService().beginKeyVerificationInDMs(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
transactionId = action.pendingRequestTransactionId,
|
|
||||||
roomId = roomId,
|
|
||||||
otherUserId = request.otherUserId,
|
|
||||||
otherDeviceId = otherDevice ?: ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
is VerificationAction.RemoteQrCodeScanned -> {
|
is VerificationAction.RemoteQrCodeScanned -> {
|
||||||
val existingTransaction = session.cryptoService().verificationService()
|
handleRemoteQrCodeScanned(action)
|
||||||
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
|
|
||||||
existingTransaction
|
|
||||||
?.userHasScannedOtherQrCode(action.scannedData)
|
|
||||||
}
|
}
|
||||||
is VerificationAction.OtherUserScannedSuccessfully -> {
|
is VerificationAction.OtherUserScannedSuccessfully -> {
|
||||||
val transactionId = state.transactionId ?: return@withState
|
handleOtherUserScannedSuccessfully(state.transactionId, otherUserId)
|
||||||
|
|
||||||
val existingTransaction = session.cryptoService().verificationService()
|
|
||||||
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
|
||||||
existingTransaction
|
|
||||||
?.otherUserScannedMyQrCode()
|
|
||||||
}
|
}
|
||||||
is VerificationAction.OtherUserDidNotScanned -> {
|
is VerificationAction.OtherUserDidNotScanned -> {
|
||||||
val transactionId = state.transactionId ?: return@withState
|
handleOtherUserDidNotScanned(state.transactionId, otherUserId)
|
||||||
|
|
||||||
val existingTransaction = session.cryptoService().verificationService()
|
|
||||||
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
|
||||||
existingTransaction
|
|
||||||
?.otherUserDidNotScannedMyQrCode()
|
|
||||||
}
|
}
|
||||||
is VerificationAction.SASMatchAction -> {
|
is VerificationAction.SASMatchAction -> {
|
||||||
(session.cryptoService().verificationService()
|
handleSASMatchAction(action)
|
||||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
|
||||||
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
|
|
||||||
}
|
}
|
||||||
is VerificationAction.SASDoNotMatchAction -> {
|
is VerificationAction.SASDoNotMatchAction -> {
|
||||||
(session.cryptoService().verificationService()
|
handleSASDoNotMatchAction(action)
|
||||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
|
||||||
as? SasVerificationTransaction)
|
|
||||||
?.shortCodeDoesNotMatch()
|
|
||||||
}
|
}
|
||||||
is VerificationAction.GotItConclusion -> {
|
is VerificationAction.GotItConclusion -> {
|
||||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||||
@ -365,6 +284,85 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleStartSASVerification(otherUserId: String, action: VerificationAction.StartSASVerification) {
|
||||||
|
val request = session.cryptoService().verificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
|
||||||
|
?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().verificationService().beginKeyVerification(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
otherUserId = request.otherUserId,
|
||||||
|
transactionId = action.pendingRequestTransactionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSASDoNotMatchAction(action: VerificationAction.SASDoNotMatchAction) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
(session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||||
|
as? SasVerificationTransaction)
|
||||||
|
?.shortCodeDoesNotMatch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSASMatchAction(action: VerificationAction.SASMatchAction) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
(session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||||
|
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleOtherUserDidNotScanned(transactionId: String?, otherUserId: String) {
|
||||||
|
transactionId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
val existingTransaction = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
||||||
|
existingTransaction
|
||||||
|
?.otherUserDidNotScannedMyQrCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleOtherUserScannedSuccessfully(transactionId: String?, otherUserId: String) {
|
||||||
|
transactionId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
val existingTransaction = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(otherUserId, transactionId) as? QrCodeVerificationTransaction
|
||||||
|
existingTransaction
|
||||||
|
?.otherUserScannedMyQrCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRemoteQrCodeScanned(action: VerificationAction.RemoteQrCodeScanned) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val existingTransaction = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
|
||||||
|
existingTransaction
|
||||||
|
?.userHasScannedOtherQrCode(action.scannedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRequestVerificationByDM(roomId: String?, otherUserId: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
|
val dmRoomId = roomId ?: session.createDirectRoom(otherUserId)
|
||||||
|
setState { copy(pendingLocalId = localId, roomId = dmRoomId) }
|
||||||
|
suspend {
|
||||||
|
session
|
||||||
|
.cryptoService()
|
||||||
|
.verificationService()
|
||||||
|
.requestKeyVerificationInDMs(
|
||||||
|
supportedVerificationMethodsProvider.provide(),
|
||||||
|
otherUserId,
|
||||||
|
dmRoomId,
|
||||||
|
localId
|
||||||
|
)
|
||||||
|
}.execute {
|
||||||
|
copy(pendingRequest = it, roomId = dmRoomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
|
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@ -446,60 +444,66 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleTransactionUpdate(state: VerificationBottomSheetViewState, tx: VerificationTransaction) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (state.selfVerificationMode && state.transactionId == null) {
|
||||||
|
// is this an incoming with that user
|
||||||
|
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
|
||||||
|
// Also auto accept incoming if needed!
|
||||||
|
// TODO is state.transactionId ever null for self verifications, doesn't seem
|
||||||
|
// like this will ever trigger
|
||||||
|
if (tx is SasVerificationTransaction && tx.state == VerificationTxState.OnStarted) {
|
||||||
|
tx.acceptVerification()
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (tx is IncomingSasVerificationTransaction) {
|
||||||
|
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
|
tx.performAccept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Use this one!
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
transactionId = tx.transactionId,
|
||||||
|
sasTransactionState = tx.state.takeIf { tx is SasVerificationTransaction },
|
||||||
|
qrTransactionState = tx.state.takeIf { tx is QrCodeVerificationTransaction }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (tx) {
|
||||||
|
is SasVerificationTransaction -> {
|
||||||
|
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||||
|
// A SAS tx has been started following this request
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
sasTransactionState = tx.state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is QrCodeVerificationTransaction -> {
|
||||||
|
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||||
|
// A QR tx has been started following this request
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
qrTransactionState = tx.state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
transactionUpdated(tx)
|
transactionUpdated(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
|
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
|
||||||
if (state.selfVerificationMode && state.transactionId == null) {
|
handleTransactionUpdate(state, tx)
|
||||||
// is this an incoming with that user
|
|
||||||
if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) {
|
|
||||||
// Also auto accept incoming if needed!
|
|
||||||
// TODO is state.transactionId ever null for self verifications, doesn't seem
|
|
||||||
// like this will ever trigger
|
|
||||||
if (tx is SasVerificationTransaction && tx.state == VerificationTxState.OnStarted) {
|
|
||||||
tx.acceptVerification()
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if (tx is IncomingSasVerificationTransaction) {
|
|
||||||
if (tx.uxState == IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
|
||||||
tx.performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// Use this one!
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
transactionId = tx.transactionId,
|
|
||||||
sasTransactionState = tx.state.takeIf { tx is SasVerificationTransaction },
|
|
||||||
qrTransactionState = tx.state.takeIf { tx is QrCodeVerificationTransaction }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (tx) {
|
|
||||||
is SasVerificationTransaction -> {
|
|
||||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
|
||||||
// A SAS tx has been started following this request
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
sasTransactionState = tx.state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is QrCodeVerificationTransaction -> {
|
|
||||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
|
||||||
// A QR tx has been started following this request
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
qrTransactionState = tx.state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
@ -514,12 +518,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
|||||||
if (!pr.isReady) {
|
if (!pr.isReady) {
|
||||||
// auto ready in this case, as we are waiting
|
// auto ready in this case, as we are waiting
|
||||||
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
// TODO, can I be here in DM mode? in this case should test if roomID is null?
|
||||||
session.cryptoService().verificationService()
|
viewModelScope.launch {
|
||||||
.readyPendingVerification(
|
session.cryptoService().verificationService()
|
||||||
supportedVerificationMethodsProvider.provide(),
|
.readyPendingVerification(
|
||||||
pr.otherUserId,
|
supportedVerificationMethodsProvider.provide(),
|
||||||
pr.transactionId ?: ""
|
pr.otherUserId,
|
||||||
)
|
pr.transactionId ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use this one!
|
// Use this one!
|
||||||
|
@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
|
|||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.matrix.android.sdk.flow.flow
|
import org.matrix.android.sdk.flow.flow
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@ -220,30 +219,27 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
// Try to initialize cross signing in background if possible
|
// Try to initialize cross signing in background if possible
|
||||||
Timber.d("Initialize cross signing...")
|
Timber.d("Initialize cross signing...")
|
||||||
try {
|
try {
|
||||||
awaitCallback<Unit> {
|
session.cryptoService().crossSigningService().initializeCrossSigning(
|
||||||
session.cryptoService().crossSigningService().initializeCrossSigning(
|
object : UserInteractiveAuthInterceptor {
|
||||||
object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
// We missed server grace period or it's not setup, see if we remember locally password
|
||||||
// We missed server grace period or it's not setup, see if we remember locally password
|
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
||||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
errCode == null &&
|
||||||
errCode == null &&
|
reAuthHelper.data != null) {
|
||||||
reAuthHelper.data != null) {
|
promise.resume(
|
||||||
promise.resume(
|
UserPasswordAuth(
|
||||||
UserPasswordAuth(
|
session = flowResponse.session,
|
||||||
session = flowResponse.session,
|
user = session.myUserId,
|
||||||
user = session.myUserId,
|
password = reAuthHelper.data
|
||||||
password = reAuthHelper.data
|
)
|
||||||
)
|
)
|
||||||
)
|
} else {
|
||||||
} else {
|
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
|
||||||
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
callback = it
|
},
|
||||||
)
|
)
|
||||||
Timber.d("Initialize cross signing SUCCESS")
|
Timber.d("Initialize cross signing SUCCESS")
|
||||||
}
|
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.e(failure, "Failed to initialize cross signing")
|
Timber.e(failure, "Failed to initialize cross signing")
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ class HomeDrawerFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
// Sign out
|
// Sign out
|
||||||
views.homeDrawerHeaderSignoutView.debouncedClicks {
|
views.homeDrawerHeaderSignoutView.debouncedClicks {
|
||||||
|
signout()
|
||||||
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
|
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
|
||||||
SignOutUiWorker(requireActivity()).perform()
|
SignOutUiWorker(requireActivity()).perform()
|
||||||
}
|
}
|
||||||
@ -118,4 +119,7 @@ class HomeDrawerFragment @Inject constructor(
|
|||||||
navigator.openDebug(requireActivity())
|
navigator.openDebug(requireActivity())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun signout() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,11 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.sample
|
import kotlinx.coroutines.flow.sample
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
@ -58,7 +59,7 @@ data class DeviceDetectionInfo(
|
|||||||
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
|
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
|
||||||
session: Session,
|
session: Session,
|
||||||
private val vectorPreferences: VectorPreferences) :
|
private val vectorPreferences: VectorPreferences) :
|
||||||
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
|
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
sealed class Action : VectorViewModelAction {
|
sealed class Action : VectorViewModelAction {
|
||||||
data class IgnoreDevice(val deviceIds: List<String>) : Action()
|
data class IgnoreDevice(val deviceIds: List<String>) : Action()
|
||||||
@ -75,12 +76,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
|
|
||||||
.firstOrNull { it.deviceId == session.sessionParams.deviceId }
|
|
||||||
?.firstTimeSeenLocalTs
|
|
||||||
?: System.currentTimeMillis()
|
|
||||||
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
|
|
||||||
|
|
||||||
ignoredDeviceList.addAll(
|
ignoredDeviceList.addAll(
|
||||||
vectorPreferences.getUnknownDeviceDismissedList().also {
|
vectorPreferences.getUnknownDeviceDismissedList().also {
|
||||||
Timber.v("## Detector - Remembered ignored list $it")
|
Timber.v("## Detector - Remembered ignored list $it")
|
||||||
@ -90,10 +85,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||||||
combine(
|
combine(
|
||||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||||
session.flow().liveMyDevicesInfo(),
|
session.flow().liveMyDevicesInfo(),
|
||||||
session.flow().liveCrossSigningPrivateKeys()
|
session.flow().liveCrossSigningPrivateKeys(),
|
||||||
) { cryptoList, infoList, pInfo ->
|
session.firstTimeDeviceSeen(),
|
||||||
|
) { cryptoList, infoList, pInfo, firstTimeDeviceSeen ->
|
||||||
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
|
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
|
||||||
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
|
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
|
||||||
|
Timber.v("## Detector - Current Session first time seen $firstTimeDeviceSeen")
|
||||||
infoList
|
infoList
|
||||||
.filter { info ->
|
.filter { info ->
|
||||||
// filter verified session, by checking the crypto device info
|
// filter verified session, by checking the crypto device info
|
||||||
@ -106,7 +103,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||||||
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
|
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
|
||||||
DeviceDetectionInfo(
|
DeviceDetectionInfo(
|
||||||
deviceInfo,
|
deviceInfo,
|
||||||
deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive,
|
deviceKnownSince > firstTimeDeviceSeen + 60_000, // short window to avoid false positive,
|
||||||
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
|
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -125,12 +122,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||||||
.sample(5_000)
|
.sample(5_000)
|
||||||
.onEach {
|
.onEach {
|
||||||
// If we have a new crypto device change, we might want to trigger refresh of device info
|
// If we have a new crypto device change, we might want to trigger refresh of device info
|
||||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
session.cryptoService().fetchDevicesList()
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
// trigger a refresh of lastSeen / last Ip
|
// trigger a refresh of lastSeen / last Ip
|
||||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().fetchDevicesList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: Action) {
|
override fun handle(action: Action) {
|
||||||
@ -154,4 +153,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
|
|||||||
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
|
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Session.firstTimeDeviceSeen() = flow {
|
||||||
|
val value = cryptoService().getCryptoDeviceInfoList(myUserId)
|
||||||
|
.firstOrNull { it.deviceId == sessionParams.deviceId }
|
||||||
|
?.firstTimeSeenLocalTs
|
||||||
|
?: System.currentTimeMillis()
|
||||||
|
emit(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -994,22 +994,24 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
||||||
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
|
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
|
||||||
if (session.cryptoService().verificationService().readyPendingVerificationInDMs(
|
viewModelScope.launch {
|
||||||
supportedVerificationMethodsProvider.provide(),
|
if (session.cryptoService().verificationService().readyPendingVerification(
|
||||||
action.otherUserId,
|
supportedVerificationMethodsProvider.provide(),
|
||||||
room.roomId,
|
action.otherUserId,
|
||||||
action.transactionId)) {
|
action.transactionId)) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
// TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
||||||
session.cryptoService().verificationService().declineVerificationRequestInDMs(
|
viewModelScope.launch {
|
||||||
action.otherUserId,
|
session.cryptoService().verificationService().cancelVerificationRequest(
|
||||||
action.transactionId,
|
action.otherUserId,
|
||||||
room.roomId)
|
action.transactionId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
|
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
|
||||||
|
@ -164,11 +164,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||||||
onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
|
onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
|
||||||
val nonNullTimelineEvent = timelineEvent() ?: return@onEach
|
val nonNullTimelineEvent = timelineEvent() ?: return@onEach
|
||||||
eventIdFlow.tryEmit(nonNullTimelineEvent.eventId)
|
eventIdFlow.tryEmit(nonNullTimelineEvent.eventId)
|
||||||
|
val events = actionsForEvent(nonNullTimelineEvent, permissions)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
eventId = nonNullTimelineEvent.eventId,
|
eventId = nonNullTimelineEvent.eventId,
|
||||||
messageBody = computeMessageBody(nonNullTimelineEvent),
|
messageBody = computeMessageBody(nonNullTimelineEvent),
|
||||||
actions = actionsForEvent(nonNullTimelineEvent, permissions)
|
actions = events
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,7 +247,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
private suspend fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
||||||
val messageContent = timelineEvent.getLastMessageContent()
|
val messageContent = timelineEvent.getLastMessageContent()
|
||||||
val msgType = messageContent?.msgType
|
val msgType = messageContent?.msgType
|
||||||
|
|
||||||
@ -317,7 +318,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||||||
// TODO sent by me or sufficient power level
|
// TODO sent by me or sufficient power level
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
|
private suspend fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
|
||||||
actionPermissions: ActionPermissions,
|
actionPermissions: ActionPermissions,
|
||||||
messageContent: MessageContent?,
|
messageContent: MessageContent?,
|
||||||
msgType: String?) {
|
msgType: String?) {
|
||||||
@ -400,7 +401,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||||||
) {
|
) {
|
||||||
add(EventSharedAction.UseKeyBackup)
|
add(EventSharedAction.UseKeyBackup)
|
||||||
}
|
}
|
||||||
if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1 ||
|
if (session.cryptoService().getCryptoDeviceInfoList(session.myUserId).size > 1 ||
|
||||||
timelineEvent.senderInfo.userId != session.myUserId) {
|
timelineEvent.senderInfo.userId != session.myUserId) {
|
||||||
add(EventSharedAction.ReRequestKey(timelineEvent.eventId))
|
add(EventSharedAction.ReRequestKey(timelineEvent.eventId))
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
|||||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.crypto.VerificationState
|
import org.matrix.android.sdk.api.crypto.VerificationState
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
@ -138,53 +139,55 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration {
|
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration = runBlocking {
|
||||||
return if (
|
if (event.root.sendState != SendState.SYNCED) {
|
||||||
event.root.sendState == SendState.SYNCED &&
|
return@runBlocking E2EDecoration.NONE
|
||||||
roomSummary?.isEncrypted.orFalse() &&
|
}
|
||||||
// is user verified
|
if (!roomSummary?.isEncrypted.orFalse()) {
|
||||||
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true) {
|
return@runBlocking E2EDecoration.NONE
|
||||||
val ts = roomSummary?.encryptionEventTs ?: 0
|
}
|
||||||
val eventTs = event.root.originServerTs ?: 0
|
val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
|
||||||
if (event.isEncrypted()) {
|
if (!isUserVerified) {
|
||||||
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
|
return@runBlocking E2EDecoration.NONE
|
||||||
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
|
}
|
||||||
E2EDecoration.NONE
|
val ts = roomSummary?.encryptionEventTs ?: 0
|
||||||
} else {
|
val eventTs = event.root.originServerTs ?: 0
|
||||||
val sendingDevice = event.root.content
|
return@runBlocking if (event.isEncrypted()) {
|
||||||
.toModel<EncryptedEventContent>()
|
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
|
||||||
?.deviceId
|
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
|
||||||
?.let { deviceId ->
|
E2EDecoration.NONE
|
||||||
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
|
|
||||||
}
|
|
||||||
when {
|
|
||||||
sendingDevice == null -> {
|
|
||||||
// For now do not decorate this with warning
|
|
||||||
// maybe it's a deleted session
|
|
||||||
E2EDecoration.NONE
|
|
||||||
}
|
|
||||||
sendingDevice.trustLevel == null -> {
|
|
||||||
E2EDecoration.WARN_SENT_BY_UNKNOWN
|
|
||||||
}
|
|
||||||
sendingDevice.trustLevel?.isVerified().orFalse() -> {
|
|
||||||
E2EDecoration.NONE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (event.root.isStateEvent()) {
|
val sendingDevice = event.root.content
|
||||||
// Do not warn for state event, they are always in clear
|
.toModel<EncryptedEventContent>()
|
||||||
E2EDecoration.NONE
|
?.deviceId
|
||||||
} else {
|
?.let { deviceId ->
|
||||||
// If event is in clear after the room enabled encryption we should warn
|
session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId)
|
||||||
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
|
}
|
||||||
|
when {
|
||||||
|
sendingDevice == null -> {
|
||||||
|
// For now do not decorate this with warning
|
||||||
|
// maybe it's a deleted session
|
||||||
|
E2EDecoration.NONE
|
||||||
|
}
|
||||||
|
sendingDevice.trustLevel == null -> {
|
||||||
|
E2EDecoration.WARN_SENT_BY_UNKNOWN
|
||||||
|
}
|
||||||
|
sendingDevice.trustLevel?.isVerified().orFalse() -> {
|
||||||
|
E2EDecoration.NONE
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
E2EDecoration.WARN_SENT_BY_UNVERIFIED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
E2EDecoration.NONE
|
if (event.root.isStateEvent()) {
|
||||||
|
// Do not warn for state event, they are always in clear
|
||||||
|
E2EDecoration.NONE
|
||||||
|
} else {
|
||||||
|
// If event is in clear after the room enabled encryption we should warn
|
||||||
|
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +100,8 @@ import im.vector.app.features.terms.ReviewTermsActivity
|
|||||||
import im.vector.app.features.widgets.WidgetActivity
|
import im.vector.app.features.widgets.WidgetActivity
|
||||||
import im.vector.app.features.widgets.WidgetArgsBuilder
|
import im.vector.app.features.widgets.WidgetArgsBuilder
|
||||||
import im.vector.app.space
|
import im.vector.app.space
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
@ -116,7 +118,8 @@ class DefaultNavigator @Inject constructor(
|
|||||||
private val widgetArgsBuilder: WidgetArgsBuilder,
|
private val widgetArgsBuilder: WidgetArgsBuilder,
|
||||||
private val appStateHandler: AppStateHandler,
|
private val appStateHandler: AppStateHandler,
|
||||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||||
private val features: VectorFeatures
|
private val features: VectorFeatures,
|
||||||
|
private val coroutineScope: CoroutineScope
|
||||||
) : Navigator {
|
) : Navigator {
|
||||||
|
|
||||||
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
|
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
|
||||||
@ -193,55 +196,57 @@ class DefaultNavigator @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
|
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
|
||||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
coroutineScope.launch {
|
||||||
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
|
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||||
?: return
|
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
|
||||||
if (tx is SasVerificationTransaction && tx.isIncoming) {
|
?: return@launch
|
||||||
tx.acceptVerification()
|
if (tx is SasVerificationTransaction && tx.isIncoming) {
|
||||||
}
|
tx.acceptVerification()
|
||||||
|
}
|
||||||
|
|
||||||
if (context is AppCompatActivity) {
|
if (context is AppCompatActivity) {
|
||||||
VerificationBottomSheet.withArgs(
|
VerificationBottomSheet.withArgs(
|
||||||
roomId = null,
|
roomId = null,
|
||||||
otherUserId = otherUserId,
|
otherUserId = otherUserId,
|
||||||
transactionId = sasTransactionId
|
transactionId = sasTransactionId
|
||||||
).show(context.supportFragmentManager, "REQPOP")
|
).show(context.supportFragmentManager, "REQPOP")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestSessionVerification(context: Context, otherSessionId: String) {
|
override fun requestSessionVerification(context: Context, otherSessionId: String) {
|
||||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
coroutineScope.launch {
|
||||||
val pr = session.cryptoService().verificationService().requestKeyVerification(
|
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||||
supportedVerificationMethodsProvider.provide(),
|
val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
|
||||||
session.myUserId,
|
supportedVerificationMethodsProvider.provide()
|
||||||
listOf(otherSessionId)
|
)
|
||||||
)
|
if (context is AppCompatActivity) {
|
||||||
if (context is AppCompatActivity) {
|
VerificationBottomSheet.withArgs(
|
||||||
VerificationBottomSheet.withArgs(
|
roomId = null,
|
||||||
roomId = null,
|
otherUserId = session.myUserId,
|
||||||
otherUserId = session.myUserId,
|
transactionId = pr.transactionId
|
||||||
transactionId = pr.transactionId
|
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||||
).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestSelfSessionVerification(context: Context) {
|
override fun requestSelfSessionVerification(context: Context) {
|
||||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
coroutineScope.launch {
|
||||||
val otherSessions = session.cryptoService()
|
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||||
.getCryptoDeviceInfo(session.myUserId)
|
val otherSessions = session.cryptoService()
|
||||||
.filter { it.deviceId != session.sessionParams.deviceId }
|
.getCryptoDeviceInfoList(session.myUserId)
|
||||||
.map { it.deviceId }
|
.filter { it.deviceId != session.sessionParams.deviceId }
|
||||||
if (context is AppCompatActivity) {
|
.map { it.deviceId }
|
||||||
if (otherSessions.isNotEmpty()) {
|
if (context is AppCompatActivity) {
|
||||||
val pr = session.cryptoService().verificationService().requestKeyVerification(
|
if (otherSessions.isNotEmpty()) {
|
||||||
supportedVerificationMethodsProvider.provide(),
|
val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
|
||||||
session.myUserId,
|
supportedVerificationMethodsProvider.provide())
|
||||||
otherSessions)
|
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
|
||||||
VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId)
|
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||||
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
} else {
|
||||||
} else {
|
VerificationBottomSheet.forSelfVerification(session)
|
||||||
VerificationBottomSheet.forSelfVerification(session)
|
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
||||||
.show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,13 +409,15 @@ class DefaultNavigator @Inject constructor(
|
|||||||
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {
|
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {
|
||||||
// if cross signing is enabled and trusted or not set up at all we should propose full 4S
|
// if cross signing is enabled and trusted or not set up at all we should propose full 4S
|
||||||
sessionHolder.getSafeActiveSession()?.let { session ->
|
sessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
|
coroutineScope.launch {
|
||||||
session.cryptoService().crossSigningService().canCrossSign()) {
|
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
|
||||||
(context as? AppCompatActivity)?.let {
|
session.cryptoService().crossSigningService().canCrossSign()) {
|
||||||
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
|
(context as? AppCompatActivity)?.let {
|
||||||
|
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ class NotifiableEventResolver @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
|
private suspend fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
|
||||||
if (root.isEncrypted() && root.mxDecryptionResult == null) {
|
if (root.isEncrypted() && root.mxDecryptionResult == null) {
|
||||||
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
// for now decrypt sync
|
// for now decrypt sync
|
||||||
|
@ -30,9 +30,9 @@ import im.vector.app.core.di.SingletonEntryPoint
|
|||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.matrix.android.sdk.flow.flow
|
import org.matrix.android.sdk.flow.flow
|
||||||
@ -124,8 +124,13 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
|
|||||||
|
|
||||||
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
|
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
|
||||||
if (!initialState.allowDeviceAction) return
|
if (!initialState.allowDeviceAction) return
|
||||||
session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, initialState.userId, action.deviceId, null)?.let { txID ->
|
viewModelScope.launch {
|
||||||
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
|
session.cryptoService().verificationService().beginDeviceVerification(
|
||||||
|
otherUserId = initialState.userId,
|
||||||
|
otherDeviceId = action.deviceId,
|
||||||
|
)?.let { txID ->
|
||||||
|
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(initialState.userId, txID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.members
|
package im.vector.app.features.roomprofile.members
|
||||||
|
|
||||||
import androidx.lifecycle.asFlow
|
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
@ -94,8 +93,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
|||||||
if (room.isEncrypted()) {
|
if (room.isEncrypted()) {
|
||||||
room.flow().liveRoomMembers(roomMemberQueryParams)
|
room.flow().liveRoomMembers(roomMemberQueryParams)
|
||||||
.flatMapLatest { membersSummary ->
|
.flatMapLatest { membersSummary ->
|
||||||
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
|
session.cryptoService().getLiveCryptoDeviceInfoList(membersSummary.map { it.userId })
|
||||||
.asFlow()
|
|
||||||
.catch { Timber.e(it) }
|
.catch { Timber.e(it) }
|
||||||
.map { deviceList ->
|
.map { deviceList ->
|
||||||
// If any key change, emit the userIds list
|
// If any key change, emit the userIds list
|
||||||
|
@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
|
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
@ -317,31 +315,32 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||||||
|
|
||||||
// Todo this should be refactored and use same state as 4S section
|
// Todo this should be refactored and use same state as 4S section
|
||||||
private fun refreshXSigningStatus() {
|
private fun refreshXSigningStatus() {
|
||||||
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
lifecycleScope.launchWhenResumed {
|
||||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||||
|
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
xSigningKeyCanSign -> {
|
xSigningKeyCanSign -> {
|
||||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
||||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
||||||
}
|
}
|
||||||
xSigningKeysAreTrusted -> {
|
xSigningKeysAreTrusted -> {
|
||||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
||||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
||||||
}
|
}
|
||||||
xSigningIsEnableInAccount -> {
|
xSigningIsEnableInAccount -> {
|
||||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
||||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
|
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
|
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
|
||||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
|
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
mCrossSigningStatePreference.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
mCrossSigningStatePreference.isVisible = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val saveMegolmStartForActivityResult = registerStartForActivityResult {
|
private val saveMegolmStartForActivityResult = registerStartForActivityResult {
|
||||||
@ -523,7 +522,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Build the cryptography preference section.
|
* Build the cryptography preference section.
|
||||||
*/
|
*/
|
||||||
private fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
|
private suspend fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
|
||||||
showDeviceListPref.isEnabled = devices.isNotEmpty()
|
showDeviceListPref.isEnabled = devices.isNotEmpty()
|
||||||
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
|
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
|
||||||
|
|
||||||
@ -553,7 +552,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// crypto section: device key (fingerprint)
|
// crypto section: device key (fingerprint)
|
||||||
val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId)
|
val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId)
|
||||||
|
|
||||||
val fingerprint = deviceInfo?.fingerprint()
|
val fingerprint = deviceInfo?.fingerprint()
|
||||||
if (fingerprint?.isNotEmpty() == true) {
|
if (fingerprint?.isNotEmpty() == true) {
|
||||||
@ -579,28 +578,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
|
||||||
private fun refreshMyDevice() {
|
private fun refreshMyDevice() {
|
||||||
session.cryptoService().getUserDevices(session.myUserId).map {
|
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
||||||
DeviceInfo(
|
session.cryptoService().getUserDevices(session.myUserId).map {
|
||||||
userId = session.myUserId,
|
DeviceInfo(
|
||||||
deviceId = it.deviceId,
|
userId = session.myUserId,
|
||||||
displayName = it.displayName()
|
deviceId = it.deviceId,
|
||||||
)
|
displayName = it.displayName()
|
||||||
}.let {
|
)
|
||||||
refreshCryptographyPreference(it)
|
}.let {
|
||||||
|
refreshCryptographyPreference(it)
|
||||||
|
}
|
||||||
|
// TODO Move to a ViewModel...
|
||||||
|
val devicesList = session.cryptoService().fetchDevicesList()
|
||||||
|
refreshCryptographyPreference(devicesList)
|
||||||
}
|
}
|
||||||
// TODO Move to a ViewModel...
|
|
||||||
session.cryptoService().fetchDevicesList(object : MatrixCallback<DevicesListResponse> {
|
|
||||||
override fun onSuccess(data: DevicesListResponse) {
|
|
||||||
if (isAdded) {
|
|
||||||
refreshCryptographyPreference(data.devices.orEmpty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
if (isAdded) {
|
|
||||||
refreshCryptographyPreference(emptyList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import im.vector.app.features.auth.ReAuthActivity
|
|||||||
import im.vector.app.features.login.ReAuthHelper
|
import im.vector.app.features.login.ReAuthHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
@ -41,7 +42,6 @@ import org.matrix.android.sdk.flow.flow
|
|||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@ -55,25 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||||||
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
|
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
combine(
|
observeCrossSigning()
|
||||||
session.flow().liveMyDevicesInfo(),
|
|
||||||
session.flow().liveCrossSigningInfo(session.myUserId)
|
|
||||||
) { myDevicesInfo, mxCrossSigningInfo ->
|
|
||||||
myDevicesInfo to mxCrossSigningInfo
|
|
||||||
}
|
|
||||||
.execute { data ->
|
|
||||||
val crossSigningKeys = data.invoke()?.second?.getOrNull()
|
|
||||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
|
||||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
|
||||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
|
||||||
|
|
||||||
copy(
|
|
||||||
crossSigningInfo = crossSigningKeys,
|
|
||||||
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
|
|
||||||
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
|
|
||||||
xSigningKeyCanSign = xSigningKeyCanSign
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var uiaContinuation: Continuation<UIABaseAuth>? = null
|
var uiaContinuation: Continuation<UIABaseAuth>? = null
|
||||||
@ -90,29 +72,27 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||||||
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
|
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
awaitCallback<Unit> {
|
session.cryptoService().crossSigningService().initializeCrossSigning(
|
||||||
session.cryptoService().crossSigningService().initializeCrossSigning(
|
object : UserInteractiveAuthInterceptor {
|
||||||
object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse,
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse,
|
errCode: String?,
|
||||||
errCode: String?,
|
promise: Continuation<UIABaseAuth>) {
|
||||||
promise: Continuation<UIABaseAuth>) {
|
Timber.d("## UIA : initializeCrossSigning UIA")
|
||||||
Timber.d("## UIA : initializeCrossSigning UIA")
|
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
||||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
|
reAuthHelper.data != null && errCode == null) {
|
||||||
reAuthHelper.data != null && errCode == null) {
|
UserPasswordAuth(
|
||||||
UserPasswordAuth(
|
session = null,
|
||||||
session = null,
|
user = session.myUserId,
|
||||||
user = session.myUserId,
|
password = reAuthHelper.data
|
||||||
password = reAuthHelper.data
|
).let { promise.resume(it) }
|
||||||
).let { promise.resume(it) }
|
} else {
|
||||||
} else {
|
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
|
||||||
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
|
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
|
||||||
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
|
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
uiaContinuation = promise
|
||||||
uiaContinuation = promise
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, it)
|
}
|
||||||
}
|
})
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
handleInitializeXSigningError(failure)
|
handleInitializeXSigningError(failure)
|
||||||
} finally {
|
} finally {
|
||||||
@ -149,6 +129,28 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeCrossSigning() {
|
||||||
|
combine(
|
||||||
|
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||||
|
session.flow().liveCrossSigningInfo(session.myUserId)
|
||||||
|
) { myDevicesInfo, mxCrossSigningInfo ->
|
||||||
|
myDevicesInfo to mxCrossSigningInfo
|
||||||
|
}.onEach { data ->
|
||||||
|
val crossSigningKeys = data.second.getOrNull()
|
||||||
|
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||||
|
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||||
|
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
crossSigningInfo = crossSigningKeys,
|
||||||
|
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
|
||||||
|
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
|
||||||
|
xSigningKeyCanSign = xSigningKeyCanSign
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleInitializeXSigningError(failure: Throwable) {
|
private fun handleInitializeXSigningError(failure: Throwable) {
|
||||||
Timber.e(failure, "## CrossSigning - Failed to initialize cross signing")
|
Timber.e(failure, "## CrossSigning - Failed to initialize cross signing")
|
||||||
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing))))
|
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing))))
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices
|
||||||
|
|
||||||
import com.airbnb.mvrx.Loading
|
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
@ -26,6 +25,7 @@ import im.vector.app.core.platform.EmptyAction
|
|||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.flow.flow
|
import org.matrix.android.sdk.flow.flow
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
@ -43,14 +43,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||||||
by hiltMavericksViewModelFactory()
|
by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
initState()
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
|
|
||||||
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
|
|
||||||
isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
session.flow().liveCrossSigningInfo(session.myUserId)
|
session.flow().liveCrossSigningInfo(session.myUserId)
|
||||||
.execute {
|
.execute {
|
||||||
copy(
|
copy(
|
||||||
@ -78,10 +71,6 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
setState {
|
|
||||||
copy(deviceInfo = Loading())
|
|
||||||
}
|
|
||||||
|
|
||||||
session.flow().liveMyDevicesInfo()
|
session.flow().liveMyDevicesInfo()
|
||||||
.map { devices ->
|
.map { devices ->
|
||||||
devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId)
|
devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId)
|
||||||
@ -91,6 +80,21 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initState() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
||||||
|
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||||
|
val isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
hasAccountCrossSigning = hasAccountCrossSigning,
|
||||||
|
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
|
||||||
|
isRecoverySetup = isRecoverySetup
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(action: EmptyAction) {
|
override fun handle(action: EmptyAction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ import im.vector.app.core.utils.PublishDataSource
|
|||||||
import im.vector.app.features.auth.ReAuthActivity
|
import im.vector.app.features.auth.ReAuthActivity
|
||||||
import im.vector.app.features.login.ReAuthHelper
|
import im.vector.app.features.login.ReAuthHelper
|
||||||
import im.vector.lib.core.utils.flow.throttleFirst
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -43,8 +42,6 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.sample
|
import kotlinx.coroutines.flow.sample
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
@ -53,17 +50,14 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
|||||||
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
import org.matrix.android.sdk.flow.flow
|
import org.matrix.android.sdk.flow.flow
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
@ -106,15 +100,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
private val refreshSource = PublishDataSource<Unit>()
|
private val refreshSource = PublishDataSource<Unit>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
initState()
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
|
|
||||||
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
|
|
||||||
myDeviceId = session.sessionParams.deviceId ?: ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||||
session.flow().liveMyDevicesInfo()
|
session.flow().liveMyDevicesInfo()
|
||||||
@ -155,7 +141,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
.sample(5_000)
|
.sample(5_000)
|
||||||
.onEach {
|
.onEach {
|
||||||
// If we have a new crypto device change, we might want to trigger refresh of device info
|
// If we have a new crypto device change, we might want to trigger refresh of device info
|
||||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
session.cryptoService().fetchDevicesList()
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
@ -168,7 +154,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
refreshSource.stream().throttleFirst(4_000)
|
refreshSource.stream().throttleFirst(4_000)
|
||||||
.onEach {
|
.onEach {
|
||||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
session.cryptoService().fetchDevicesList()
|
||||||
session.cryptoService().downloadKeys(listOf(session.myUserId), true)
|
session.cryptoService().downloadKeys(listOf(session.myUserId), true)
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
@ -176,6 +162,21 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
queryRefreshDevicesList()
|
queryRefreshDevicesList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initState() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
||||||
|
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||||
|
val myDeviceId = session.sessionParams.deviceId ?: ""
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
hasAccountCrossSigning = hasAccountCrossSigning,
|
||||||
|
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
|
||||||
|
myDeviceId = myDeviceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
session.cryptoService().verificationService().removeListener(this)
|
session.cryptoService().verificationService().removeListener(this)
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
@ -240,13 +241,15 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
|
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
|
||||||
val txID = session.cryptoService()
|
viewModelScope.launch {
|
||||||
.verificationService()
|
val txID = session.cryptoService()
|
||||||
.beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null)
|
.verificationService()
|
||||||
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
|
.beginDeviceVerification(session.myUserId, action.deviceId)
|
||||||
session.myUserId,
|
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
|
||||||
txID
|
session.myUserId,
|
||||||
))
|
txID
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
|
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
|
||||||
@ -268,8 +271,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// legacy
|
// legacy
|
||||||
session.cryptoService().setDeviceVerification(
|
session.cryptoService().verificationService().markedLocallyAsManuallyVerified(
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
|
||||||
action.cryptoDeviceInfo.userId,
|
action.cryptoDeviceInfo.userId,
|
||||||
action.cryptoDeviceInfo.deviceId)
|
action.cryptoDeviceInfo.deviceId)
|
||||||
}
|
}
|
||||||
@ -288,27 +290,21 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRename(action: DevicesAction.Rename) {
|
private fun handleRename(action: DevicesAction.Rename) {
|
||||||
session.cryptoService().setDeviceName(action.deviceId, action.newName, object : MatrixCallback<Unit> {
|
viewModelScope.launch {
|
||||||
override fun onSuccess(data: Unit) {
|
try {
|
||||||
|
session.cryptoService().setDeviceName(action.deviceId, action.newName)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(request = Success(Unit))
|
||||||
request = Success(data)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// force settings update
|
// force settings update
|
||||||
queryRefreshDevicesList()
|
queryRefreshDevicesList()
|
||||||
}
|
} catch (failure: Throwable) {
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(request = Fail(failure))
|
||||||
request = Fail(failure)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -323,39 +319,32 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
awaitCallback<Unit> {
|
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
|
||||||
session.cryptoService().deleteDevice(deviceId, object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
Timber.d("## UIA : deleteDevice UIA")
|
||||||
Timber.d("## UIA : deleteDevice UIA")
|
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
|
||||||
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) {
|
UserPasswordAuth(
|
||||||
UserPasswordAuth(
|
session = null,
|
||||||
session = null,
|
user = session.myUserId,
|
||||||
user = session.myUserId,
|
password = reAuthHelper.data
|
||||||
password = reAuthHelper.data
|
).let { promise.resume(it) }
|
||||||
).let { promise.resume(it) }
|
} else {
|
||||||
} else {
|
Timber.d("## UIA : deleteDevice UIA > start reauth activity")
|
||||||
Timber.d("## UIA : deleteDevice UIA > start reauth activity")
|
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
|
||||||
_viewEvents.post(DevicesViewEvents.RequestReAuth(flowResponse, errCode))
|
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
uiaContinuation = promise
|
||||||
uiaContinuation = promise
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, it)
|
}
|
||||||
}
|
})
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(request = Success(Unit))
|
||||||
request = Success(Unit)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// force settings update
|
|
||||||
queryRefreshDevicesList()
|
queryRefreshDevicesList()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(request = Fail(failure))
|
||||||
request = Fail(failure)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
|
if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
|
||||||
_viewEvents.post(DevicesViewEvents.Failure(Exception(stringProvider.getString(R.string.authentication_error))))
|
_viewEvents.post(DevicesViewEvents.Failure(Exception(stringProvider.getString(R.string.authentication_error))))
|
||||||
|
@ -95,7 +95,7 @@ class SoftLogoutActivity : LoginActivity() {
|
|||||||
MainActivity.restartApp(this, MainActivityArgs())
|
MainActivity.restartApp(this, MainActivityArgs())
|
||||||
}
|
}
|
||||||
|
|
||||||
views.loginLoading.isVisible = softLogoutViewState.isLoading()
|
views.loginLoading.isVisible = softLogoutViewState.isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -34,6 +34,7 @@ import im.vector.app.features.signout.soft.epoxy.loginRedButtonItem
|
|||||||
import im.vector.app.features.signout.soft.epoxy.loginTextItem
|
import im.vector.app.features.signout.soft.epoxy.loginTextItem
|
||||||
import im.vector.app.features.signout.soft.epoxy.loginTitleItem
|
import im.vector.app.features.signout.soft.epoxy.loginTitleItem
|
||||||
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
|
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SoftLogoutController @Inject constructor(
|
class SoftLogoutController @Inject constructor(
|
||||||
@ -52,6 +53,7 @@ class SoftLogoutController @Inject constructor(
|
|||||||
|
|
||||||
override fun buildModels() {
|
override fun buildModels() {
|
||||||
val safeViewState = viewState ?: return
|
val safeViewState = viewState ?: return
|
||||||
|
if (safeViewState.hasUnsavedKeys is Incomplete) return
|
||||||
|
|
||||||
buildHeader(safeViewState)
|
buildHeader(safeViewState)
|
||||||
buildForm(safeViewState)
|
buildForm(safeViewState)
|
||||||
@ -78,7 +80,7 @@ class SoftLogoutController @Inject constructor(
|
|||||||
state.userDisplayName,
|
state.userDisplayName,
|
||||||
state.userId))
|
state.userId))
|
||||||
}
|
}
|
||||||
if (state.hasUnsavedKeys) {
|
if (state.hasUnsavedKeys().orFalse()) {
|
||||||
loginTextItem {
|
loginTextItem {
|
||||||
id("signText2")
|
id("signText2")
|
||||||
text(host.stringProvider.getString(R.string.soft_logout_signin_e2e_warning_notice))
|
text(host.stringProvider.getString(R.string.soft_logout_signin_e2e_warning_notice))
|
||||||
|
@ -32,6 +32,7 @@ import im.vector.app.features.login.AbstractLoginFragment
|
|||||||
import im.vector.app.features.login.LoginAction
|
import im.vector.app.features.login.LoginAction
|
||||||
import im.vector.app.features.login.LoginMode
|
import im.vector.app.features.login.LoginMode
|
||||||
import im.vector.app.features.login.LoginViewEvents
|
import im.vector.app.features.login.LoginViewEvents
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,7 +119,7 @@ class SoftLogoutFragment @Inject constructor(
|
|||||||
withState(softLogoutViewModel) { state ->
|
withState(softLogoutViewModel) { state ->
|
||||||
cleanupUi()
|
cleanupUi()
|
||||||
|
|
||||||
val messageResId = if (state.hasUnsavedKeys) {
|
val messageResId = if (state.hasUnsavedKeys().orFalse()) {
|
||||||
R.string.soft_logout_clear_data_dialog_e2e_warning_content
|
R.string.soft_logout_clear_data_dialog_e2e_warning_content
|
||||||
} else {
|
} else {
|
||||||
R.string.soft_logout_clear_data_dialog_content
|
R.string.soft_logout_clear_data_dialog_content
|
||||||
|
@ -69,7 +69,6 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
userId = userId,
|
userId = userId,
|
||||||
deviceId = session.sessionParams.deviceId.orEmpty(),
|
deviceId = session.sessionParams.deviceId.orEmpty(),
|
||||||
userDisplayName = session.getUser(userId)?.displayName ?: userId,
|
userDisplayName = session.getUser(userId)?.displayName ?: userId,
|
||||||
hasUnsavedKeys = session.hasUnsavedKeys()
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
SoftLogoutViewState(
|
SoftLogoutViewState(
|
||||||
@ -77,17 +76,25 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
userId = "",
|
userId = "",
|
||||||
deviceId = "",
|
deviceId = "",
|
||||||
userDisplayName = "",
|
userDisplayName = "",
|
||||||
hasUnsavedKeys = false
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
checkHasUnsavedKeys()
|
||||||
// Get the supported login flow
|
// Get the supported login flow
|
||||||
getSupportedLoginFlow()
|
getSupportedLoginFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkHasUnsavedKeys() {
|
||||||
|
suspend {
|
||||||
|
session.hasUnsavedKeys()
|
||||||
|
}.execute {
|
||||||
|
copy(hasUnsavedKeys = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSupportedLoginFlow() {
|
private fun getSupportedLoginFlow() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
authenticationService.cancelPendingLoginOrRegistration()
|
authenticationService.cancelPendingLoginOrRegistration()
|
||||||
|
@ -30,13 +30,12 @@ data class SoftLogoutViewState(
|
|||||||
val userId: String,
|
val userId: String,
|
||||||
val deviceId: String,
|
val deviceId: String,
|
||||||
val userDisplayName: String,
|
val userDisplayName: String,
|
||||||
val hasUnsavedKeys: Boolean,
|
val hasUnsavedKeys: Async<Boolean> = Uninitialized,
|
||||||
val enteredPassword: String = ""
|
val enteredPassword: String = ""
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
fun isLoading(): Boolean {
|
val isLoading: Boolean =
|
||||||
return asyncLoginAction is Loading ||
|
asyncLoginAction is Loading ||
|
||||||
// Keep loading when it is success because of the delay to switch to the next Activity
|
// Keep loading when it is success because of the delay to switch to the next Activity
|
||||||
asyncLoginAction is Success
|
asyncLoginAction is Success
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -130,14 +130,14 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
/**
|
/**
|
||||||
* Safe way to get the number of keys to backup
|
* Safe way to get the number of keys to backup
|
||||||
*/
|
*/
|
||||||
fun getNumberOfKeysToBackup(): Int {
|
private suspend fun getNumberOfKeysToBackup(): Int {
|
||||||
return session.cryptoService().inboundGroupSessionsCount(false)
|
return session.cryptoService().inboundGroupSessionsCount(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safe way to tell if there are more keys on the server
|
* Safe way to tell if there are more keys on the server
|
||||||
*/
|
*/
|
||||||
fun canRestoreKeys(): Boolean {
|
private suspend fun canRestoreKeys(): Boolean {
|
||||||
return session.cryptoService().keysBackupService().canRestoreKeys()
|
return session.cryptoService().keysBackupService().canRestoreKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,5 +161,5 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: EmptyAction) {}
|
override fun handle(action: EmptyAction) = Unit
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,25 @@
|
|||||||
package im.vector.app.features.workers.signout
|
package im.vector.app.features.workers.signout
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cannotLogoutSafely
|
import im.vector.app.core.extensions.cannotLogoutSafely
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
import im.vector.app.core.extensions.singletonEntryPoint
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
import im.vector.app.features.MainActivityArgs
|
import im.vector.app.features.MainActivityArgs
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
class SignOutUiWorker(private val activity: FragmentActivity) {
|
class SignOutUiWorker(private val activity: FragmentActivity) {
|
||||||
|
|
||||||
fun perform() {
|
fun perform() {
|
||||||
val session = activity.singletonEntryPoint().activeSessionHolder().getSafeActiveSession() ?: return
|
val session = activity.singletonEntryPoint().activeSessionHolder().getSafeActiveSession() ?: return
|
||||||
|
activity.lifecycleScope.perform(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.perform(session: Session) = launch {
|
||||||
if (session.cannotLogoutSafely()) {
|
if (session.cannotLogoutSafely()) {
|
||||||
// The backup check on logout flow has to be displayed if there are keys in the store, and the keys backup state is not Ready
|
// The backup check on logout flow has to be displayed if there are keys in the store, and the keys backup state is not Ready
|
||||||
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance()
|
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance()
|
||||||
|
@ -30,9 +30,9 @@ class FakeCryptoService : CryptoService by mockk() {
|
|||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
|
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
|
override fun getLiveCryptoDeviceInfoList(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
|
override fun getLiveCryptoDeviceInfoList(userIds: List<String>) = MutableLiveData(
|
||||||
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
|
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user