mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 16:22:41 +02:00
Compare commits
6 Commits
develop
...
feature/bc
Author | SHA1 | Date | |
---|---|---|---|
|
f1311eef1a | ||
|
eb10956bc0 | ||
|
b00b327d43 | ||
|
1d5f8cbe04 | ||
|
9d0d93b89a | ||
|
135c92e613 |
@@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
|||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
@@ -63,6 +62,7 @@ import org.matrix.android.sdk.api.session.securestorage.KeyRef
|
|||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.awaitCallback
|
import org.matrix.android.sdk.api.util.awaitCallback
|
||||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@@ -523,18 +523,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
try {
|
session.cryptoService().decryptAndUpdateEvent(event, "")
|
||||||
session.cryptoService().decryptEvent(event, "").let { result ->
|
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error: MXCryptoError) {
|
|
||||||
// nop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
|
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
|
||||||
event.getClearType() == EventType.MESSAGE &&
|
event.getClearType() == EventType.MESSAGE &&
|
||||||
@@ -548,15 +537,15 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
sentEventIds.forEach { sentEventId ->
|
sentEventIds.forEach { sentEventId ->
|
||||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
try {
|
when (val result = session.cryptoService().decryptEvent(event, "")) {
|
||||||
session.cryptoService().decryptEvent(event, "")
|
is DecryptionResult.Failure -> {
|
||||||
fail("Should not be able to decrypt event")
|
val errorType = (result.error as? MXCryptoError.Base)?.errorType ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT
|
||||||
} catch (error: MXCryptoError) {
|
if (expectedError != null) {
|
||||||
val errorType = (error as? MXCryptoError.Base)?.errorType
|
assertEquals("Unexpected reason", expectedError, errorType)
|
||||||
if (expectedError == null) {
|
}
|
||||||
assertNotNull(errorType)
|
}
|
||||||
} else {
|
is DecryptionResult.Success -> {
|
||||||
assertEquals("Unexpected reason", expectedError, errorType)
|
fail("Should not be able to decrypt event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,21 +55,23 @@ class DecryptRedactedEventTest : InstrumentedTest {
|
|||||||
val eventBobPov = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)!!
|
val eventBobPov = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)!!
|
||||||
|
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
try {
|
bobSession.cryptoService().decryptEvent(eventBobPov.root, "").fold(
|
||||||
val result = bobSession.cryptoService().decryptEvent(eventBobPov.root, "")
|
{ result ->
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Unexpected redacted reason",
|
"Unexpected redacted reason",
|
||||||
redactionReason,
|
redactionReason,
|
||||||
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.content?.get("reason")
|
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.content?.get("reason")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Unexpected Redacted event id",
|
"Unexpected Redacted event id",
|
||||||
timelineEvent.eventId,
|
timelineEvent.eventId,
|
||||||
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.redacts
|
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.redacts
|
||||||
)
|
)
|
||||||
} catch (failure: Throwable) {
|
},
|
||||||
Assert.fail("Should not throw when decrypting a redacted event")
|
{ err, _ ->
|
||||||
}
|
Assert.fail("Should not throw when decrypting a redacted event actual ${err.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.amshove.kluent.fail
|
|
||||||
import org.amshove.kluent.internal.assertEquals
|
import org.amshove.kluent.internal.assertEquals
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
@@ -61,7 +60,7 @@ import org.matrix.android.sdk.common.RetryTestRule
|
|||||||
import org.matrix.android.sdk.common.SessionTestParams
|
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.common.TestMatrixCallback
|
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||||
import org.matrix.android.sdk.mustFail
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
|
// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
|
||||||
@@ -504,17 +503,17 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
// Confirm we can decrypt one but not the other
|
// Confirm we can decrypt one but not the other
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
mustFail(message = "Should not be able to decrypt event") {
|
Assert.assertTrue(
|
||||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
"Should not be able to decrypt event",
|
||||||
}
|
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") is DecryptionResult.Failure
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
try {
|
Assert.assertTrue(
|
||||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
"Should be able to decrypt event",
|
||||||
} catch (error: MXCryptoError) {
|
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "") is DecryptionResult.Success
|
||||||
fail("Should be able to decrypt event")
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now let's verify bobs session, and re-request keys
|
// Now let's verify bobs session, and re-request keys
|
||||||
@@ -535,22 +534,15 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
// we should be able to decrypt both
|
// we should be able to decrypt both
|
||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
testHelper.retryPeriodicallyWithLatch(it) {
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
val canDecryptFirst = try {
|
val canDecryptFirst =
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||||
}
|
} is DecryptionResult.Success
|
||||||
true
|
|
||||||
} catch (error: MXCryptoError) {
|
val canDecryptSecond =
|
||||||
false
|
testHelper.runBlockingTest {
|
||||||
}
|
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||||
val canDecryptSecond = try {
|
} is DecryptionResult.Success
|
||||||
testHelper.runBlockingTest {
|
|
||||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} catch (error: MXCryptoError) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
canDecryptFirst && canDecryptSecond
|
canDecryptFirst && canDecryptSecond
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
|
|||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
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.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
@@ -40,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||||
@@ -226,13 +226,10 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
testHelper.waitWithLatch {
|
testHelper.waitWithLatch {
|
||||||
testHelper.retryPeriodicallyWithLatch(it) {
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
// we should get back the key and be able to decrypt
|
// we should get back the key and be able to decrypt
|
||||||
val result = testHelper.runBlockingTest {
|
val canDecrypt = testHelper.runBlockingTest {
|
||||||
tryOrNull {
|
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
} is DecryptionResult.Success
|
||||||
}
|
canDecrypt
|
||||||
}
|
|
||||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
|
||||||
result != null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -46,7 +46,7 @@ import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
|||||||
import org.matrix.android.sdk.common.RetryTestRule
|
import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
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.mustFail
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@@ -95,9 +95,10 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
assert(receivedEvent!!.isEncrypted())
|
assert(receivedEvent!!.isEncrypted())
|
||||||
|
|
||||||
commonTestHelper.runBlockingTest {
|
commonTestHelper.runBlockingTest {
|
||||||
mustFail {
|
assertTrue(
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
"Should not be able to decrypt",
|
||||||
}
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") is DecryptionResult.Failure
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
@@ -166,9 +167,10 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commonTestHelper.runBlockingTest {
|
commonTestHelper.runBlockingTest {
|
||||||
mustFail {
|
assertTrue(
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
"Should not be able to decrypt",
|
||||||
}
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") is DecryptionResult.Failure
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the device as trusted
|
// Mark the device as trusted
|
||||||
|
@@ -20,6 +20,7 @@ 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 org.junit.Assert
|
import org.junit.Assert
|
||||||
|
import org.junit.Assert.fail
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -27,7 +28,6 @@ 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.NoOpMatrixCallback
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
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.crypto.RequestResult
|
import org.matrix.android.sdk.api.session.crypto.RequestResult
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
@@ -41,7 +41,6 @@ import org.matrix.android.sdk.common.MockOkHttpInterceptor
|
|||||||
import org.matrix.android.sdk.common.RetryTestRule
|
import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
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.mustFail
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@@ -96,17 +95,16 @@ 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
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
mustFail(
|
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "").fold(
|
||||||
message = "This session should not be able to decrypt",
|
{
|
||||||
failureBlock = { failure ->
|
fail("This session should not be able to decrypt")
|
||||||
|
},
|
||||||
|
{ failure, code ->
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
val technicalMessage = failure.technicalMessage
|
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, type)
|
||||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, code?.value)
|
||||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
|
||||||
}
|
}
|
||||||
) {
|
)
|
||||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's see if the reply we got from bob first session is unverified
|
// Let's see if the reply we got from bob first session is unverified
|
||||||
@@ -139,16 +137,16 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
// Previous message should still be undecryptable (partially withheld session)
|
// Previous message should still be undecryptable (partially withheld session)
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
mustFail(
|
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "").fold(
|
||||||
message = "This session should not be able to decrypt",
|
{
|
||||||
failureBlock = { failure ->
|
fail("This session should not be able to decrypt")
|
||||||
|
},
|
||||||
|
{ failure, code ->
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
val technicalMessage = failure.technicalMessage
|
Assert.assertEquals("Error should be partially withheld", MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, type)
|
||||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, code?.value)
|
||||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
}
|
||||||
}) {
|
)
|
||||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,16 +185,16 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
mustFail(
|
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "").fold(
|
||||||
message = "This session should not be able to decrypt",
|
{
|
||||||
failureBlock = { failure ->
|
fail("This session should not be able to decrypt")
|
||||||
|
},
|
||||||
|
{ failure, code ->
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
val technicalMessage = failure.technicalMessage
|
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, type)
|
||||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, code?.value)
|
||||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
|
}
|
||||||
}) {
|
)
|
||||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that alice has marked the session to be shared with bob
|
// Ensure that alice has marked the session to be shared with bob
|
||||||
@@ -262,10 +260,8 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
|
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
|
||||||
// try to decrypt and force key request
|
// try to decrypt and force key request
|
||||||
tryOrNull {
|
testHelper.runBlockingTest {
|
||||||
testHelper.runBlockingTest {
|
bobSecondSession.cryptoService().decryptEvent(it.root, "")
|
||||||
bobSecondSession.cryptoService().decryptEvent(it.root, "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto.replayattack
|
package org.matrix.android.sdk.internal.crypto.replayattack
|
||||||
|
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import org.amshove.kluent.internal.assertFailsWith
|
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
@@ -64,11 +63,15 @@ class ReplayAttackTest : InstrumentedTest {
|
|||||||
// Lets decrypt the original event
|
// Lets decrypt the original event
|
||||||
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||||
// Lets decrypt the fake event that will have the same message index
|
// Lets decrypt the fake event that will have the same message index
|
||||||
val exception = assertFailsWith<MXCryptoError.Base> {
|
|
||||||
// An exception should be thrown while the same index would have been used for the previous decryption
|
aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId).fold(
|
||||||
aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId)
|
{
|
||||||
}
|
fail("Decryption should have fail because of duplicate index")
|
||||||
assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, exception.errorType)
|
},
|
||||||
|
{ err, _ ->
|
||||||
|
assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, (err as MXCryptoError.Base).errorType)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
cryptoTestData.cleanUp(testHelper)
|
cryptoTestData.cleanUp(testHelper)
|
||||||
}
|
}
|
||||||
|
@@ -34,12 +34,12 @@ import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
|||||||
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
|
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
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.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
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.content.RoomKeyWithHeldContent
|
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
@@ -145,10 +145,9 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun discardOutboundSession(roomId: String)
|
fun discardOutboundSession(roomId: String)
|
||||||
|
|
||||||
@Throws(MXCryptoError::class)
|
suspend fun decryptEvent(event: Event, timeline: String): DecryptionResult
|
||||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
|
||||||
|
|
||||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
suspend fun decryptAndUpdateEvent(event: Event, timeline: String)
|
||||||
|
|
||||||
fun getEncryptionAlgorithm(roomId: String): String?
|
fun getEncryptionAlgorithm(roomId: String): String?
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.crypto
|
|||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +61,7 @@ sealed class MXCryptoError : Throwable() {
|
|||||||
OLM,
|
OLM,
|
||||||
UNKNOWN_DEVICES,
|
UNKNOWN_DEVICES,
|
||||||
UNKNOWN_MESSAGE_INDEX,
|
UNKNOWN_MESSAGE_INDEX,
|
||||||
KEYS_WITHHELD
|
// KEYS_WITHHELD
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -91,4 +92,6 @@ sealed class MXCryptoError : Throwable() {
|
|||||||
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." +
|
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." +
|
||||||
" Perhaps the homeserver is hiding the configuration event."
|
" Perhaps the homeserver is hiding the configuration event."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toResult() = DecryptionResult.Failure(this)
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
|
|||||||
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.crypto.model.OlmDecryptionResult
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
||||||
@@ -99,6 +100,13 @@ data class Event(
|
|||||||
@Transient
|
@Transient
|
||||||
var mCryptoErrorReason: String? = null
|
var mCryptoErrorReason: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the session is unknown (UISI) or paritially known (unknown message index)
|
||||||
|
* we can have more info from the session owner on why it was not sent.
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
var mCryptoWithHeldCode: WithHeldCode? = null
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
var sendState: SendState = SendState.UNKNOWN
|
var sendState: SendState = SendState.UNKNOWN
|
||||||
|
|
||||||
@@ -131,6 +139,7 @@ data class Event(
|
|||||||
it.mxDecryptionResult = mxDecryptionResult
|
it.mxDecryptionResult = mxDecryptionResult
|
||||||
it.mCryptoError = mCryptoError
|
it.mCryptoError = mCryptoError
|
||||||
it.mCryptoErrorReason = mCryptoErrorReason
|
it.mCryptoErrorReason = mCryptoErrorReason
|
||||||
|
it.mCryptoWithHeldCode = mCryptoWithHeldCode
|
||||||
it.sendState = sendState
|
it.sendState = sendState
|
||||||
it.ageLocalTs = ageLocalTs
|
it.ageLocalTs = ageLocalTs
|
||||||
it.threadDetails = threadDetails
|
it.threadDetails = threadDetails
|
||||||
@@ -278,6 +287,7 @@ data class Event(
|
|||||||
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
||||||
if (mCryptoError != other.mCryptoError) return false
|
if (mCryptoError != other.mCryptoError) return false
|
||||||
if (mCryptoErrorReason != other.mCryptoErrorReason) return false
|
if (mCryptoErrorReason != other.mCryptoErrorReason) return false
|
||||||
|
if (mCryptoWithHeldCode != other.mCryptoWithHeldCode) return false
|
||||||
if (sendState != other.sendState) return false
|
if (sendState != other.sendState) return false
|
||||||
if (threadDetails != other.threadDetails) return false
|
if (threadDetails != other.threadDetails) return false
|
||||||
return true
|
return true
|
||||||
@@ -297,6 +307,7 @@ data class Event(
|
|||||||
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
||||||
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
||||||
result = 31 * result + (mCryptoErrorReason?.hashCode() ?: 0)
|
result = 31 * result + (mCryptoErrorReason?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (mCryptoWithHeldCode?.hashCode() ?: 0)
|
||||||
result = 31 * result + sendState.hashCode()
|
result = 31 * result + sendState.hashCode()
|
||||||
result = 31 * result + threadDetails.hashCode()
|
result = 31 * result + threadDetails.hashCode()
|
||||||
|
|
||||||
|
@@ -61,7 +61,12 @@ data class TimelineEvent(
|
|||||||
val ownedByThreadChunk: Boolean = false,
|
val ownedByThreadChunk: Boolean = false,
|
||||||
val senderInfo: SenderInfo,
|
val senderInfo: SenderInfo,
|
||||||
val annotations: EventAnnotationsSummary? = null,
|
val annotations: EventAnnotationsSummary? = null,
|
||||||
val readReceipts: List<ReadReceipt> = emptyList()
|
val readReceipts: List<ReadReceipt> = emptyList(),
|
||||||
|
/**
|
||||||
|
* If true the SDK is currently trying to get the keys from other sessions.
|
||||||
|
* will only be set when used with Timeline, if not will be null
|
||||||
|
*/
|
||||||
|
val hasActiveRequestForKeys: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@@ -57,8 +57,8 @@ import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
|||||||
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
|
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
|
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.TrailType
|
import org.matrix.android.sdk.api.session.crypto.model.TrailType
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
@@ -75,6 +75,7 @@ import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
|||||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
|
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
|
||||||
@@ -752,34 +753,35 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the MXEventDecryptionResult data, or throw in case of error
|
* @return the MXEventDecryptionResult data, or throw in case of error
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
override suspend fun decryptEvent(event: Event, timeline: String): DecryptionResult {
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
|
||||||
return internalDecryptEvent(event, timeline)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>) {
|
|
||||||
eventDecryptor.decryptEventAsync(event, timeline, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt an event.
|
|
||||||
*
|
|
||||||
* @param event the raw event.
|
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
|
||||||
* @return the MXEventDecryptionResult data, or null in case of error
|
|
||||||
*/
|
|
||||||
@Throws(MXCryptoError::class)
|
|
||||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
|
||||||
return eventDecryptor.decryptEvent(event, timeline)
|
return eventDecryptor.decryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the event and udpdates the event fields with the results.
|
||||||
|
*/
|
||||||
|
override suspend fun decryptAndUpdateEvent(event: Event, timeline: String) {
|
||||||
|
eventDecryptor.decryptEvent(event, timeline).fold(
|
||||||
|
{ decryptionResult ->
|
||||||
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
|
payload = decryptionResult.clearEvent,
|
||||||
|
senderKey = decryptionResult.senderCurve25519Key,
|
||||||
|
keysClaimed = decryptionResult.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
||||||
|
forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
||||||
|
)
|
||||||
|
// clear errors
|
||||||
|
event.mCryptoError = null
|
||||||
|
event.mCryptoErrorReason = null
|
||||||
|
event.mCryptoWithHeldCode = null
|
||||||
|
},
|
||||||
|
{ error, code ->
|
||||||
|
event.mCryptoError = (error as? MXCryptoError.Base)?.errorType ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT
|
||||||
|
event.mCryptoErrorReason = (error as? MXCryptoError.Base)?.technicalMessage ?: error.message
|
||||||
|
event.mCryptoWithHeldCode = code
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset replay attack data for the given timeline.
|
* Reset replay attack data for the given timeline.
|
||||||
*
|
*
|
||||||
|
@@ -16,18 +16,16 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
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.crypto.MXCRYPTO_ALGORITHM_OLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
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.crypto.model.MXEventDecryptionResult
|
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
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.content.OlmEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
|
||||||
@@ -35,9 +33,9 @@ import org.matrix.android.sdk.api.session.events.model.toContent
|
|||||||
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.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
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.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
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.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -49,7 +47,6 @@ private val loggerTag = LoggerTag("EventDecryptor", LoggerTag.CRYPTO)
|
|||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class EventDecryptor @Inject constructor(
|
internal class EventDecryptor @Inject constructor(
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val roomDecryptorProvider: RoomDecryptorProvider,
|
private val roomDecryptorProvider: RoomDecryptorProvider,
|
||||||
@@ -80,24 +77,26 @@ internal class EventDecryptor @Inject constructor(
|
|||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the MXEventDecryptionResult data, or throw in case of error
|
* @return the MXEventDecryptionResult data, or throw in case of error
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
suspend fun decryptEvent(event: Event, timeline: String): DecryptionResult {
|
||||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
|
||||||
return internalDecryptEvent(event, timeline)
|
return internalDecryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
suspend fun decryptAndUpdate(event: Event, timeline: String) {
|
||||||
* Decrypt an event asynchronously.
|
val result = internalDecryptEvent(event, timeline)
|
||||||
*
|
when (result) {
|
||||||
* @param event the raw event.
|
is DecryptionResult.Success -> {
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
val decryptionResult = result.decryptedResult
|
||||||
* @param callback the callback to return data or null
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
*/
|
payload = decryptionResult.clearEvent,
|
||||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
senderKey = decryptionResult.senderCurve25519Key,
|
||||||
// is it needed to do that on the crypto scope??
|
keysClaimed = decryptionResult.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
||||||
runCatching {
|
)
|
||||||
internalDecryptEvent(event, timeline)
|
}
|
||||||
}.foldToCallback(callback)
|
is DecryptionResult.Failure -> {
|
||||||
|
event.mCryptoError = (result.error as? MXCryptoError.Base)?.errorType ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT
|
||||||
|
event.mCryptoErrorReason = (result.error as? MXCryptoError.Base)?.technicalMessage ?: result.error.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,20 +107,23 @@ internal class EventDecryptor @Inject constructor(
|
|||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the MXEventDecryptionResult data, or null in case of error
|
* @return the MXEventDecryptionResult data, or null in case of error
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
private suspend fun internalDecryptEvent(event: Event, timeline: String): DecryptionResult {
|
||||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
|
||||||
val eventContent = event.content
|
val eventContent = event.content
|
||||||
if (eventContent == null) {
|
if (eventContent == null) {
|
||||||
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
return DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||||
|
)
|
||||||
} else if (event.isRedacted()) {
|
} else if (event.isRedacted()) {
|
||||||
// we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm
|
// we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm
|
||||||
return MXEventDecryptionResult(
|
return DecryptionResult.Success(
|
||||||
clearEvent = mapOf(
|
MXEventDecryptionResult(
|
||||||
"room_id" to event.roomId.orEmpty(),
|
clearEvent = mapOf(
|
||||||
"type" to EventType.MESSAGE,
|
"room_id" to event.roomId.orEmpty(),
|
||||||
"content" to emptyMap<String, Any>(),
|
"type" to EventType.MESSAGE,
|
||||||
"unsigned" to event.unsignedData.toContent()
|
"content" to emptyMap<String, Any>(),
|
||||||
|
"unsigned" to event.unsignedData.toContent()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -130,25 +132,27 @@ internal class EventDecryptor @Inject constructor(
|
|||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||||
Timber.tag(loggerTag.value).e("decryptEvent() : $reason")
|
Timber.tag(loggerTag.value).e("decryptEvent() : $reason")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
return DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
try {
|
return alg.decryptEvent(event, timeline).also {
|
||||||
return alg.decryptEvent(event, timeline)
|
if (it is DecryptionResult.Failure) {
|
||||||
} catch (mxCryptoError: MXCryptoError) {
|
val mxCryptoError = it.error
|
||||||
Timber.tag(loggerTag.value).d("internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
|
Timber.tag(loggerTag.value).d("internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
|
||||||
if (algorithm == MXCRYPTO_ALGORITHM_OLM) {
|
if (algorithm == MXCRYPTO_ALGORITHM_OLM) {
|
||||||
if (mxCryptoError is MXCryptoError.Base &&
|
if (mxCryptoError is MXCryptoError.Base &&
|
||||||
mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
|
mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
|
||||||
// need to find sending device
|
// need to find sending device
|
||||||
val olmContent = event.content.toModel<OlmEventContent>()
|
val olmContent = event.content.toModel<OlmEventContent>()
|
||||||
if (event.senderId != null && olmContent?.senderKey != null) {
|
if (event.senderId != null && olmContent?.senderKey != null) {
|
||||||
markOlmSessionForUnwedging(event.senderId, olmContent.senderKey)
|
markOlmSessionForUnwedging(event.senderId, olmContent.senderKey)
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("Can't mark as wedge malformed")
|
Timber.tag(loggerTag.value).d("Can't mark as wedge malformed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw mxCryptoError
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,27 @@ package org.matrix.android.sdk.internal.crypto.algorithms
|
|||||||
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.crypto.model.MXEventDecryptionResult
|
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
||||||
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.content.WithHeldCode
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
|
|
||||||
|
fun MXEventDecryptionResult.toResult() = DecryptionResult.Success(this)
|
||||||
|
|
||||||
|
sealed class DecryptionResult {
|
||||||
|
data class Success(val decryptedResult: MXEventDecryptionResult) : DecryptionResult()
|
||||||
|
data class Failure(val error: MXCryptoError, val withheldInfo: WithHeldCode? = null) : DecryptionResult()
|
||||||
|
|
||||||
|
fun fold(success: (MXEventDecryptionResult) -> Unit, error: (MXCryptoError, WithHeldCode?) -> Unit) {
|
||||||
|
when (this) {
|
||||||
|
is Success -> {
|
||||||
|
success(this.decryptedResult)
|
||||||
|
}
|
||||||
|
is Failure -> {
|
||||||
|
error(this.error, this.withheldInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for decrypting data.
|
* An interface for decrypting data.
|
||||||
*/
|
*/
|
||||||
@@ -33,8 +52,7 @@ internal interface IMXDecrypting {
|
|||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
* @return the decryption information, or an error
|
* @return the decryption information, or an error
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
suspend fun decryptEvent(event: Event, timeline: String): DecryptionResult
|
||||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a key event.
|
* Handle a key event.
|
||||||
|
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
|
|||||||
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.internal.crypto.MXOlmDevice
|
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
|
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
@@ -54,29 +55,33 @@ internal class MXMegolmDecryption(
|
|||||||
*/
|
*/
|
||||||
// private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
// private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
||||||
|
|
||||||
@Throws(MXCryptoError::class)
|
override suspend fun decryptEvent(event: Event, timeline: String): DecryptionResult {
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
|
||||||
return decryptEvent(event, timeline, true)
|
return decryptEvent(event, timeline, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(MXCryptoError::class)
|
private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): DecryptionResult {
|
||||||
private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
|
||||||
Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
|
Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
|
||||||
if (event.roomId.isNullOrBlank()) {
|
if (event.roomId.isNullOrBlank()) {
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
return DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||||
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
?: return DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
)
|
||||||
|
|
||||||
if (encryptedEventContent.senderKey.isNullOrBlank() ||
|
if (encryptedEventContent.senderKey.isNullOrBlank() ||
|
||||||
encryptedEventContent.sessionId.isNullOrBlank() ||
|
encryptedEventContent.sessionId.isNullOrBlank() ||
|
||||||
encryptedEventContent.ciphertext.isNullOrBlank()) {
|
encryptedEventContent.ciphertext.isNullOrBlank()) {
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
return DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runCatching {
|
try {
|
||||||
olmDevice.decryptGroupMessage(
|
val olmDecryptionResult = olmDevice.decryptGroupMessage(
|
||||||
encryptedEventContent.ciphertext,
|
encryptedEventContent.ciphertext,
|
||||||
event.roomId,
|
event.roomId,
|
||||||
timeline,
|
timeline,
|
||||||
@@ -84,85 +89,73 @@ internal class MXMegolmDecryption(
|
|||||||
encryptedEventContent.sessionId,
|
encryptedEventContent.sessionId,
|
||||||
encryptedEventContent.senderKey
|
encryptedEventContent.senderKey
|
||||||
)
|
)
|
||||||
}
|
return if (olmDecryptionResult.payload != null) {
|
||||||
.fold(
|
MXEventDecryptionResult(
|
||||||
{ olmDecryptionResult ->
|
clearEvent = olmDecryptionResult.payload,
|
||||||
// the decryption succeeds
|
senderCurve25519Key = olmDecryptionResult.senderKey,
|
||||||
if (olmDecryptionResult.payload != null) {
|
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||||
MXEventDecryptionResult(
|
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
||||||
clearEvent = olmDecryptionResult.payload,
|
.orEmpty()
|
||||||
senderCurve25519Key = olmDecryptionResult.senderKey,
|
).also {
|
||||||
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
liveEventManager.get().dispatchLiveEventDecrypted(event, it)
|
||||||
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
}.let {
|
||||||
.orEmpty()
|
DecryptionResult.Success(it)
|
||||||
).also {
|
}
|
||||||
liveEventManager.get().dispatchLiveEventDecrypted(event, it)
|
} else {
|
||||||
}
|
return DecryptionResult.Failure(
|
||||||
} else {
|
MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throwable ->
|
|
||||||
liveEventManager.get().dispatchLiveEventDecryptionFailed(event, throwable)
|
|
||||||
if (throwable is MXCryptoError.OlmError) {
|
|
||||||
// TODO Check the value of .message
|
|
||||||
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
|
||||||
// So we know that session, but it's ratcheted and we can't decrypt at that index
|
|
||||||
|
|
||||||
if (requestKeysOnFail) {
|
|
||||||
requestKeysForEvent(event)
|
|
||||||
}
|
|
||||||
// Check if partially withheld
|
|
||||||
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
|
|
||||||
if (withHeldInfo != null) {
|
|
||||||
// Encapsulate as withHeld exception
|
|
||||||
throw MXCryptoError.Base(
|
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD,
|
|
||||||
withHeldInfo.code?.value ?: "",
|
|
||||||
withHeldInfo.reason
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw MXCryptoError.Base(
|
|
||||||
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
|
|
||||||
"UNKNOWN_MESSAGE_INDEX",
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
|
||||||
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
|
||||||
|
|
||||||
throw MXCryptoError.Base(
|
|
||||||
MXCryptoError.ErrorType.OLM,
|
|
||||||
reason,
|
|
||||||
detailedReason
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (throwable is MXCryptoError.Base) {
|
|
||||||
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
|
||||||
// Check if it was withheld by sender to enrich error code
|
|
||||||
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
|
|
||||||
if (withHeldInfo != null) {
|
|
||||||
if (requestKeysOnFail) {
|
|
||||||
requestKeysForEvent(event)
|
|
||||||
}
|
|
||||||
// Encapsulate as withHeld exception
|
|
||||||
throw MXCryptoError.Base(
|
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD,
|
|
||||||
withHeldInfo.code?.value ?: "",
|
|
||||||
withHeldInfo.reason
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestKeysOnFail) {
|
|
||||||
requestKeysForEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw throwable
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
} catch (olmError: MXCryptoError.OlmError) {
|
||||||
|
liveEventManager.get().dispatchLiveEventDecryptionFailed(event, olmError)
|
||||||
|
return if (olmError.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
||||||
|
// So we know that session, but it's ratcheted and we can't decrypt at that index
|
||||||
|
DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
|
||||||
|
"UNKNOWN_MESSAGE_INDEX",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
// Check if partially withheld
|
||||||
|
cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)?.code
|
||||||
|
).also {
|
||||||
|
if (requestKeysOnFail) {
|
||||||
|
requestKeysForEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.OLM,
|
||||||
|
String.format(MXCryptoError.OLM_REASON, olmError.olmException.message),
|
||||||
|
String.format(MXCryptoError.DETAILED_OLM_REASON, event.eventId, olmError.olmException.message)
|
||||||
|
)
|
||||||
|
).also {
|
||||||
|
if (requestKeysOnFail) {
|
||||||
|
requestKeysForEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (cryptoError: MXCryptoError) {
|
||||||
|
liveEventManager.get().dispatchLiveEventDecryptionFailed(event, cryptoError)
|
||||||
|
return DecryptionResult.Failure(
|
||||||
|
cryptoError,
|
||||||
|
cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)?.code
|
||||||
|
).also {
|
||||||
|
if (requestKeysOnFail) {
|
||||||
|
requestKeysForEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (other: Throwable) {
|
||||||
|
Timber.e("Unexpected decryption error, should not happen", other)
|
||||||
|
return DecryptionResult.Failure(
|
||||||
|
MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||||
|
other.localizedMessage ?: "Unexpected error"
|
||||||
|
),
|
||||||
|
cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)?.code
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -27,7 +27,9 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
|||||||
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.toResult
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.util.convertFromUTF8
|
import org.matrix.android.sdk.internal.util.convertFromUTF8
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -42,36 +44,36 @@ internal class MXOlmDecryption(
|
|||||||
) :
|
) :
|
||||||
IMXDecrypting {
|
IMXDecrypting {
|
||||||
|
|
||||||
@Throws(MXCryptoError::class)
|
override suspend fun decryptEvent(event: Event, timeline: String): DecryptionResult {
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
val olmEventContent = event.content.toModel<OlmEventContent>()
|
||||||
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
?: return MXCryptoError.Base(
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : bad event format")
|
MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||||
throw MXCryptoError.Base(
|
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON
|
||||||
MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
).toResult().also {
|
||||||
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON
|
Timber.tag(loggerTag.value).e("## decryptEvent() : bad event format")
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val cipherText = olmEventContent.ciphertext ?: run {
|
val cipherText = olmEventContent.ciphertext
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : missing cipher text")
|
?: return MXCryptoError.Base(
|
||||||
throw MXCryptoError.Base(
|
MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||||
MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
MXCryptoError.MISSING_CIPHER_TEXT_REASON
|
||||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON
|
).toResult().also {
|
||||||
)
|
Timber.tag(loggerTag.value).e("## decryptEvent() : missing cipher text")
|
||||||
}
|
}
|
||||||
|
|
||||||
val senderKey = olmEventContent.senderKey ?: run {
|
val senderKey = olmEventContent.senderKey
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : missing sender key")
|
?: return MXCryptoError.Base(
|
||||||
throw MXCryptoError.Base(
|
MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||||
MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON
|
||||||
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON
|
).toResult().also {
|
||||||
)
|
Timber.tag(loggerTag.value).e("## decryptEvent() : missing sender key")
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
|
val messageAny = cipherText[olmDevice.deviceCurve25519Key]
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
|
?: return MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
|
.toResult().also {
|
||||||
}
|
Timber.tag(loggerTag.value).e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
|
||||||
|
}
|
||||||
|
|
||||||
// The message for myUser
|
// The message for myUser
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -81,8 +83,9 @@ internal class MXOlmDecryption(
|
|||||||
|
|
||||||
if (decryptedPayload == null) {
|
if (decryptedPayload == null) {
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
Timber.tag(loggerTag.value).e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
return MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
val payloadString = convertFromUTF8(decryptedPayload)
|
val payloadString = convertFromUTF8(decryptedPayload)
|
||||||
|
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||||
@@ -90,92 +93,97 @@ internal class MXOlmDecryption(
|
|||||||
|
|
||||||
if (payload == null) {
|
if (payload == null) {
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent failed : null payload")
|
Timber.tag(loggerTag.value).e("## decryptEvent failed : null payload")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)
|
return MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
|
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString)
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : bad olmPayloadContent format")
|
?: return MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
|
.toResult().also {
|
||||||
}
|
Timber.tag(loggerTag.value).e("## decryptEvent() : bad olmPayloadContent format")
|
||||||
|
}
|
||||||
|
|
||||||
if (olmPayloadContent.recipient.isNullOrBlank()) {
|
if (olmPayloadContent.recipient.isNullOrBlank()) {
|
||||||
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
|
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : $reason")
|
return MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
|
.toResult().also {
|
||||||
|
Timber.tag(loggerTag.value).e("## decryptEvent() : $reason")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (olmPayloadContent.recipient != userId) {
|
if (olmPayloadContent.recipient != userId) {
|
||||||
Timber.tag(loggerTag.value).e(
|
return MXCryptoError.Base(
|
||||||
"## decryptEvent() : Event ${event.eventId}:" +
|
|
||||||
" Intended recipient ${olmPayloadContent.recipient} does not match our id $userId"
|
|
||||||
)
|
|
||||||
throw MXCryptoError.Base(
|
|
||||||
MXCryptoError.ErrorType.BAD_RECIPIENT,
|
MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||||
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)
|
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)
|
||||||
)
|
).toResult().also {
|
||||||
|
Timber.tag(loggerTag.value).e(
|
||||||
|
"## decryptEvent() : Event ${event.eventId}:" +
|
||||||
|
" Intended recipient ${olmPayloadContent.recipient} does not match our id $userId"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val recipientKeys = olmPayloadContent.recipientKeys ?: run {
|
val recipientKeys = olmPayloadContent.recipientKeys
|
||||||
Timber.tag(loggerTag.value).e(
|
?: return MXCryptoError.Base(
|
||||||
"## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
|
MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||||
" property; cannot prevent unknown-key attack"
|
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")
|
||||||
)
|
).toResult().also {
|
||||||
throw MXCryptoError.Base(
|
Timber.tag(loggerTag.value).e(
|
||||||
MXCryptoError.ErrorType.MISSING_PROPERTY,
|
"## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
|
||||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")
|
" property; cannot prevent unknown-key attack"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val ed25519 = recipientKeys["ed25519"]
|
val ed25519 = recipientKeys["ed25519"]
|
||||||
|
|
||||||
if (ed25519 != olmDevice.deviceEd25519Key) {
|
if (ed25519 != olmDevice.deviceEd25519Key) {
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
|
Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
|
||||||
throw MXCryptoError.Base(
|
return MXCryptoError.Base(
|
||||||
MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||||
MXCryptoError.BAD_RECIPIENT_KEY_REASON
|
MXCryptoError.BAD_RECIPIENT_KEY_REASON
|
||||||
)
|
).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (olmPayloadContent.sender.isNullOrBlank()) {
|
if (olmPayloadContent.sender.isNullOrBlank()) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
|
.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
|
||||||
throw MXCryptoError.Base(
|
return MXCryptoError.Base(
|
||||||
MXCryptoError.ErrorType.MISSING_PROPERTY,
|
MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")
|
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")
|
||||||
)
|
).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (olmPayloadContent.sender != event.senderId) {
|
if (olmPayloadContent.sender != event.senderId) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.e("Event ${event.eventId}: sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
|
.e("Event ${event.eventId}: sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
|
||||||
throw MXCryptoError.Base(
|
return MXCryptoError.Base(
|
||||||
MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||||
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)
|
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)
|
||||||
)
|
).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// that's strange we don't use olm in room anymore
|
||||||
if (olmPayloadContent.roomId != event.roomId) {
|
if (olmPayloadContent.roomId != event.roomId) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
|
.e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
|
||||||
throw MXCryptoError.Base(
|
return MXCryptoError.Base(
|
||||||
MXCryptoError.ErrorType.BAD_ROOM,
|
MXCryptoError.ErrorType.BAD_ROOM,
|
||||||
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId)
|
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId)
|
||||||
)
|
).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
val keys = olmPayloadContent.keys ?: run {
|
val keys = olmPayloadContent.keys
|
||||||
Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys")
|
?: return MXCryptoError.Base(
|
||||||
throw MXCryptoError.Base(
|
MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||||
MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
MXCryptoError.MISSING_CIPHER_TEXT_REASON
|
||||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON
|
).toResult().also {
|
||||||
)
|
Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
return MXEventDecryptionResult(
|
return MXEventDecryptionResult(
|
||||||
clearEvent = payload,
|
clearEvent = payload,
|
||||||
senderCurve25519Key = senderKey,
|
senderCurve25519Key = senderKey,
|
||||||
claimedEd25519Key = keys["ed25519"]
|
claimedEd25519Key = keys["ed25519"]
|
||||||
)
|
).toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1260,6 +1260,9 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
if (newState == OutgoingRoomKeyRequestState.UNSENT) {
|
if (newState == OutgoingRoomKeyRequestState.UNSENT) {
|
||||||
// clear the old replies
|
// clear the old replies
|
||||||
this.replies.deleteAllFromRealm()
|
this.replies.deleteAllFromRealm()
|
||||||
|
this.creationTimeStamp = clock.epochMillis()
|
||||||
|
} else if (newState == OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND) {
|
||||||
|
this.creationTimeStamp = clock.epochMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1658,6 +1661,9 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
val roomId = withHeldContent.roomId ?: return
|
val roomId = withHeldContent.roomId ?: return
|
||||||
val sessionId = withHeldContent.sessionId ?: return
|
val sessionId = withHeldContent.sessionId ?: return
|
||||||
if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return
|
if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return
|
||||||
|
Timber.tag(loggerTag.value).v(
|
||||||
|
"WithHeldMegolmSession set for $roomId|$sessionId is ${withHeldContent.code}"
|
||||||
|
)
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
doRealmTransaction(realmConfiguration) { realm ->
|
||||||
WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let {
|
WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let {
|
||||||
it.code = withHeldContent.code
|
it.code = withHeldContent.code
|
||||||
@@ -1679,6 +1685,10 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
senderKey = it.senderKey
|
senderKey = it.senderKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}.also {
|
||||||
|
Timber.tag(loggerTag.value).v(
|
||||||
|
"WithHeldMegolmSession get for $roomId|$sessionId is ${it?.code}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
|
|||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
|
||||||
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
|
||||||
import org.matrix.android.sdk.internal.util.Normalizer
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -60,7 +61,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 35L,
|
schemaVersion = 36L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
@@ -105,5 +106,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
if (oldVersion < 33) MigrateSessionTo033(realm).perform()
|
if (oldVersion < 33) MigrateSessionTo033(realm).perform()
|
||||||
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
|
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
|
||||||
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
|
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
|
||||||
|
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
|||||||
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.api.session.room.threads.model.ThreadSummary
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||||
@@ -219,23 +220,26 @@ private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEnt
|
|||||||
cryptoService ?: return
|
cryptoService ?: return
|
||||||
val event = eventEntity.asDomain()
|
val event = eventEntity.asDomain()
|
||||||
if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
|
if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
|
||||||
try {
|
Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
|
||||||
Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
|
// 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
|
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
||||||
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
|
||||||
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
|
when (result) {
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
is DecryptionResult.Success -> {
|
||||||
payload = result.clearEvent,
|
val decryptedResult = result.decryptedResult
|
||||||
senderKey = result.senderCurve25519Key,
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
payload = decryptedResult.clearEvent,
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
senderKey = decryptedResult.senderCurve25519Key,
|
||||||
)
|
keysClaimed = decryptedResult.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
||||||
// Save decryption result, to not decrypt every time we enter the thread list
|
forwardingCurve25519KeyChain = decryptedResult.forwardingCurve25519KeyChain
|
||||||
eventEntity.setDecryptionResult(result)
|
)
|
||||||
} catch (e: MXCryptoError) {
|
// Save decryption result, to not decrypt every time we enter the thread list
|
||||||
if (e is MXCryptoError.Base) {
|
eventEntity.setDecryptionResult(decryptedResult)
|
||||||
event.mCryptoError = e.errorType
|
}
|
||||||
event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
is DecryptionResult.Failure -> {
|
||||||
|
event.mCryptoError = (result.error as? MXCryptoError.Base)?.errorType ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT
|
||||||
|
event.mCryptoErrorReason = (result.error as? MXCryptoError.Base)?.technicalMessage ?: result.error.message
|
||||||
|
event.mCryptoWithHeldCode = result.withheldInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
|||||||
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.UnsignedData
|
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||||
import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
|
import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
|
||||||
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.api.session.room.sender.SenderInfo
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
@@ -56,6 +57,7 @@ internal object EventMapper {
|
|||||||
}
|
}
|
||||||
eventEntity.decryptionErrorReason = event.mCryptoErrorReason
|
eventEntity.decryptionErrorReason = event.mCryptoErrorReason
|
||||||
eventEntity.decryptionErrorCode = event.mCryptoError?.name
|
eventEntity.decryptionErrorCode = event.mCryptoError?.name
|
||||||
|
eventEntity.decryptionWithheldCode = event.mCryptoWithHeldCode?.value
|
||||||
eventEntity.isRootThread = event.threadDetails?.isRootThread ?: false
|
eventEntity.isRootThread = event.threadDetails?.isRootThread ?: false
|
||||||
eventEntity.rootThreadEventId = event.getRootThreadEventId()
|
eventEntity.rootThreadEventId = event.getRootThreadEventId()
|
||||||
eventEntity.numberOfThreads = event.threadDetails?.numberOfThreads ?: 0
|
eventEntity.numberOfThreads = event.threadDetails?.numberOfThreads ?: 0
|
||||||
@@ -90,18 +92,23 @@ internal object EventMapper {
|
|||||||
it.ageLocalTs = eventEntity.ageLocalTs
|
it.ageLocalTs = eventEntity.ageLocalTs
|
||||||
it.sendState = eventEntity.sendState
|
it.sendState = eventEntity.sendState
|
||||||
it.sendStateDetails = eventEntity.sendStateDetails
|
it.sendStateDetails = eventEntity.sendStateDetails
|
||||||
eventEntity.decryptionResultJson?.let { json ->
|
|
||||||
|
val decryptionJson = eventEntity.decryptionResultJson
|
||||||
|
if (decryptionJson != null) {
|
||||||
try {
|
try {
|
||||||
it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
|
it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(decryptionJson)
|
||||||
} catch (t: JsonDataException) {
|
} catch (t: JsonDataException) {
|
||||||
Timber.e(t, "Failed to parse decryption result")
|
Timber.e(t, "Failed to parse decryption result")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TODO get the full crypto error object
|
||||||
|
eventEntity.decryptionErrorCode?.let { errorCode ->
|
||||||
|
it.mCryptoError = MXCryptoError.ErrorType.valueOf(errorCode)
|
||||||
|
it.mCryptoWithHeldCode = WithHeldCode.fromCode(eventEntity.decryptionWithheldCode)
|
||||||
|
it.mCryptoErrorReason = eventEntity.decryptionErrorReason
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO get the full crypto error object
|
|
||||||
it.mCryptoError = eventEntity.decryptionErrorCode?.let { errorCode ->
|
|
||||||
MXCryptoError.ErrorType.valueOf(errorCode)
|
|
||||||
}
|
|
||||||
it.mCryptoErrorReason = eventEntity.decryptionErrorReason
|
|
||||||
it.threadDetails = ThreadDetails(
|
it.threadDetails = ThreadDetails(
|
||||||
isRootThread = eventEntity.isRootThread,
|
isRootThread = eventEntity.isRootThread,
|
||||||
isThread = if (it.threadDetails?.isThread == true) true else eventEntity.isThread(),
|
isThread = if (it.threadDetails?.isThread == true) true else eventEntity.isThread(),
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.database.migration
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||||
|
|
||||||
|
internal class MigrateSessionTo036(realm: DynamicRealm) : RealmMigrator(realm, 36) {
|
||||||
|
|
||||||
|
override fun doMigrate(realm: DynamicRealm) {
|
||||||
|
// add initial withheld code as part as the event entity
|
||||||
|
realm.schema.get("EventEntity")
|
||||||
|
?.addField(EventEntityFields.DECRYPTION_WITHHELD_CODE, String::class.java)
|
||||||
|
?.transform { dynamicObject ->
|
||||||
|
// previously the withheld code was stored in mCryptoErrorReason when the error type
|
||||||
|
// was KEYS_WITHHELD but now we are storing it properly for both type UNKNOWN_INBOUND_SESSION_ID and UNKNOWN_MESSAGE_INDEX
|
||||||
|
val reasonString = dynamicObject.getString(EventEntityFields.DECRYPTION_ERROR_REASON)
|
||||||
|
val errorCode = dynamicObject.getString(EventEntityFields.DECRYPTION_ERROR_CODE)
|
||||||
|
// try to see if it's a withheld code
|
||||||
|
val tryAsCode = WithHeldCode.fromCode(reasonString)
|
||||||
|
if (errorCode != null && tryAsCode != null) {
|
||||||
|
// ok migrate to proper code
|
||||||
|
dynamicObject.setString(EventEntityFields.DECRYPTION_WITHHELD_CODE, tryAsCode.value)
|
||||||
|
// the former code was WITHHELD, we can't know if it was unknown session or partially withheld, so assume unknown
|
||||||
|
// we could try to see if session is known but is it worth it?
|
||||||
|
dynamicObject.setString(EventEntityFields.DECRYPTION_WITHHELD_CODE, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -79,6 +79,11 @@ internal open class EventEntity(
|
|||||||
if (value != field) field = value
|
if (value != field) field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var decryptionWithheldCode: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
||||||
fun setDecryptionResult(result: MXEventDecryptionResult) {
|
fun setDecryptionResult(result: MXEventDecryptionResult) {
|
||||||
@@ -93,6 +98,7 @@ internal open class EventEntity(
|
|||||||
decryptionResultJson = adapter.toJson(decryptionResult)
|
decryptionResultJson = adapter.toJson(decryptionResult)
|
||||||
decryptionErrorCode = null
|
decryptionErrorCode = null
|
||||||
decryptionErrorReason = null
|
decryptionErrorReason = null
|
||||||
|
decryptionWithheldCode = null
|
||||||
|
|
||||||
// If we have an EventInsertEntity for the eventId we make sures it can be processed now.
|
// If we have an EventInsertEntity for the eventId we make sures it can be processed now.
|
||||||
realm.where(EventInsertEntity::class.java)
|
realm.where(EventInsertEntity::class.java)
|
||||||
|
@@ -17,8 +17,6 @@ package org.matrix.android.sdk.internal.session.room.relation.threads
|
|||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
|
||||||
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.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
@@ -214,27 +212,6 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
|
|||||||
return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
|
return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke the event decryption mechanism for a specific event.
|
|
||||||
*/
|
|
||||||
private suspend fun decryptIfNeeded(event: Event, roomId: String) {
|
|
||||||
try {
|
|
||||||
// Event from sync does not have roomId, so add it to the event first
|
|
||||||
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
} catch (e: MXCryptoError) {
|
|
||||||
if (e is MXCryptoError.Base) {
|
|
||||||
event.mCryptoError = e.errorType
|
|
||||||
event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleReaction(
|
private fun handleReaction(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
@@ -20,7 +20,7 @@ import io.realm.Realm
|
|||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
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.content.EncryptionEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
@@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState
|
|||||||
import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary
|
import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary
|
||||||
import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications
|
import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications
|
||||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
@@ -80,7 +81,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
||||||
if (roomSummaryEntity != null) {
|
if (roomSummaryEntity != null) {
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
latestPreviewableEvent?.attemptToDecrypt()
|
latestPreviewableEvent?.attemptToDecrypt(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,14 +130,14 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
|
Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
|
||||||
|
|
||||||
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
||||||
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
|
Timber.v("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
|
||||||
|
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
|
|
||||||
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
||||||
if (lastActivityFromEvent != null) {
|
if (lastActivityFromEvent != null) {
|
||||||
roomSummaryEntity.lastActivityTime = lastActivityFromEvent
|
roomSummaryEntity.lastActivityTime = lastActivityFromEvent
|
||||||
latestPreviewableEvent.attemptToDecrypt()
|
latestPreviewableEvent.attemptToDecrypt(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 ||
|
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 ||
|
||||||
@@ -186,17 +187,27 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TimelineEventEntity.attemptToDecrypt() {
|
private fun TimelineEventEntity.attemptToDecrypt(force: Boolean) {
|
||||||
when (val root = this.root) {
|
when (val root = this.root) {
|
||||||
null -> {
|
null -> {
|
||||||
Timber.v("Decryption skipped due to missing root event $eventId")
|
Timber.v("Decryption skipped due to missing root event $eventId")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (root.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
|
if (root.type == EventType.ENCRYPTED) {
|
||||||
Timber.v("Should decrypt $eventId")
|
if (force || (root.decryptionResultJson == null && root.decryptionErrorCode == null)) {
|
||||||
tryOrNull {
|
Timber.v("Should decrypt $eventId")
|
||||||
runBlocking { eventDecryptor.decryptEvent(root.asDomain(), "") }
|
when (val res = runBlocking { eventDecryptor.decryptEvent(root.asDomain(), "") }) {
|
||||||
}?.let { root.setDecryptionResult(it) }
|
is DecryptionResult.Success -> {
|
||||||
|
root.setDecryptionResult(res.decryptedResult)
|
||||||
|
}
|
||||||
|
is DecryptionResult.Failure -> {
|
||||||
|
val error = res.error
|
||||||
|
root.decryptionErrorCode = (error as? MXCryptoError.Base)?.errorType?.name ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT.name
|
||||||
|
root.decryptionErrorReason = (error as? MXCryptoError.Base)?.technicalMessage ?: error.message
|
||||||
|
root.decryptionWithheldCode = res.withheldInfo?.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.session.room.timeline
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmChangeListener
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.internal.closeQuietly
|
||||||
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
|
||||||
|
import org.matrix.android.sdk.internal.util.createBackgroundHandler
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.Timer
|
||||||
|
import java.util.TimerTask
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
private val loggerTag = LoggerTag("KeyGossipStatusManager", LoggerTag.CRYPTO)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by DefaultTimeline to decorates timeline events with some crypto database related information.
|
||||||
|
* The controller will then be able to update the display of events.
|
||||||
|
*/
|
||||||
|
internal class CryptoInfoEventDecorator(
|
||||||
|
private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val roomId: String,
|
||||||
|
private val clock: Clock,
|
||||||
|
private val rebuildListener: RebuildListener,
|
||||||
|
private val onEventsUpdated: (Boolean) -> Unit,
|
||||||
|
) : RealmChangeListener<RealmResults<OutgoingKeyRequestEntity>> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread")
|
||||||
|
const val MAX_AGE_FOR_REQUEST_TO_BE_IDLE = 60_000L
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher()
|
||||||
|
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
|
||||||
|
private val sequencer = SemaphoreCoroutineSequencer()
|
||||||
|
|
||||||
|
private var outgoingRequestList: RealmResults<OutgoingKeyRequestEntity>? = null
|
||||||
|
|
||||||
|
private var decoratedEvents = mutableMapOf<String, MutableList<String>>()
|
||||||
|
|
||||||
|
private var activeOutgoingRequestsForSession = emptyList<String>()
|
||||||
|
private var realmInstance = AtomicReference<Realm>()
|
||||||
|
private val isStarted = AtomicBoolean(false)
|
||||||
|
|
||||||
|
private var tickerTimer: Timer? = null
|
||||||
|
|
||||||
|
private var isE2EE = false
|
||||||
|
|
||||||
|
fun getActiveRequestForSession(): List<String> {
|
||||||
|
return synchronized(this) {
|
||||||
|
activeOutgoingRequestsForSession.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
scope.launch {
|
||||||
|
sequencer.post {
|
||||||
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
|
realmInstance.set(Realm.getInstance(realmConfiguration))
|
||||||
|
|
||||||
|
realmInstance.get().where<CryptoRoomEntity>()
|
||||||
|
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
|
||||||
|
.findFirst()?.let {
|
||||||
|
isE2EE = it.wasEncryptedOnce ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = clock.epochMillis()
|
||||||
|
// we can limit the query to the most recent request at the time of creation of the CryptoInfoEventDecorator
|
||||||
|
val createdAfter = now - MAX_AGE_FOR_REQUEST_TO_BE_IDLE
|
||||||
|
outgoingRequestList = realmInstance.get().where<OutgoingKeyRequestEntity>()
|
||||||
|
.equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId)
|
||||||
|
.`in`(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR,
|
||||||
|
listOf(
|
||||||
|
OutgoingRoomKeyRequestState.UNSENT,
|
||||||
|
OutgoingRoomKeyRequestState.SENT,
|
||||||
|
OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND,
|
||||||
|
OutgoingRoomKeyRequestState.SENT_THEN_CANCELED,
|
||||||
|
)
|
||||||
|
.map { it.name }
|
||||||
|
.toTypedArray()
|
||||||
|
)
|
||||||
|
.greaterThan(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, createdAfter)
|
||||||
|
.findAllAsync().also {
|
||||||
|
it?.addChangeListener(this@CryptoInfoEventDecorator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decorateTimelineEvent(event: TimelineEvent): TimelineEvent {
|
||||||
|
if (!isE2EE) return event
|
||||||
|
if (event.root.getClearType() != EventType.ENCRYPTED) return event
|
||||||
|
val megolmSessionId = event.root.content?.get("session_id") as? String ?: return event
|
||||||
|
// we might want to remember the built events per megolm session
|
||||||
|
synchronized(this) {
|
||||||
|
decoratedEvents.getOrPut(megolmSessionId) { mutableListOf() }.add(event.eventId)
|
||||||
|
}
|
||||||
|
val hasActiveRequests = getActiveRequestForSession().contains(megolmSessionId)
|
||||||
|
return if (hasActiveRequests) {
|
||||||
|
event.copy(
|
||||||
|
hasActiveRequestForKeys = hasActiveRequests
|
||||||
|
)
|
||||||
|
} else event
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
scope.coroutineContext.cancelChildren()
|
||||||
|
scope.launch {
|
||||||
|
sequencer.post {
|
||||||
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
|
outgoingRequestList?.removeAllChangeListeners()
|
||||||
|
decoratedEvents.clear()
|
||||||
|
outgoingRequestList = null
|
||||||
|
realmInstance.get().closeQuietly()
|
||||||
|
tickerTimer?.cancel()
|
||||||
|
tickerTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChange(results: RealmResults<OutgoingKeyRequestEntity>) {
|
||||||
|
if (!results.isLoaded || results.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Timber.tag(loggerTag.value).v("OutgoingKeyRequests data changes")
|
||||||
|
val now = clock.epochMillis()
|
||||||
|
val newList = mutableListOf<String>()
|
||||||
|
// we consider a request active for 1mn?
|
||||||
|
results.forEach {
|
||||||
|
if (!it.isValid) return@forEach
|
||||||
|
val creationTs = it.creationTimeStamp ?: return@forEach
|
||||||
|
val megolmSessionId = it.megolmSessionId ?: return@forEach
|
||||||
|
val isActive = when (it.requestState) {
|
||||||
|
OutgoingRoomKeyRequestState.UNSENT -> true
|
||||||
|
OutgoingRoomKeyRequestState.SENT -> true
|
||||||
|
OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> false
|
||||||
|
OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> false
|
||||||
|
OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> true
|
||||||
|
}
|
||||||
|
val requestAge = now - creationTs
|
||||||
|
|
||||||
|
if (isActive && requestAge < MAX_AGE_FOR_REQUEST_TO_BE_IDLE) {
|
||||||
|
// we consider as active
|
||||||
|
newList.add(megolmSessionId)
|
||||||
|
Timber.tag(loggerTag.value).v("Adding active request for megolmSessionId $megolmSessionId age:$requestAge")
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).v("Ignoring inactive request for megolmSessionId $megolmSessionId age:$requestAge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sessionsToRebuild = mutableSetOf<String>()
|
||||||
|
val builtMap = mutableMapOf<String, List<String>>()
|
||||||
|
val oldActive = getActiveRequestForSession()
|
||||||
|
synchronized(this) {
|
||||||
|
sessionsToRebuild.addAll(oldActive)
|
||||||
|
sessionsToRebuild.addAll(newList)
|
||||||
|
activeOutgoingRequestsForSession = newList
|
||||||
|
builtMap.putAll(decoratedEvents)
|
||||||
|
}
|
||||||
|
sessionsToRebuild
|
||||||
|
.flatMap { builtMap[it] ?: emptyList() }
|
||||||
|
.forEach { eventId ->
|
||||||
|
rebuildListener.rebuildEvent(eventId) {
|
||||||
|
decorateTimelineEvent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEventsUpdated(true)
|
||||||
|
// do we need to schedule a ticker update next?
|
||||||
|
if (newList.isNotEmpty()) {
|
||||||
|
// yes we have active ones, we should refresh
|
||||||
|
tickerTimer?.cancel()
|
||||||
|
tickerTimer = Timer()
|
||||||
|
tickerTimer?.schedule(
|
||||||
|
object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
Timber.tag(loggerTag.value).v("OutgoingKeyRequests ticker")
|
||||||
|
scope.launch {
|
||||||
|
outgoingRequestList?.let {
|
||||||
|
onChange(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MAX_AGE_FOR_REQUEST_TO_BE_IDLE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -58,6 +58,7 @@ internal class DefaultTimeline(
|
|||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val initialEventId: String?,
|
private val initialEventId: String?,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
|
cryptoRealmConfiguration: RealmConfiguration,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val readReceiptHandler: ReadReceiptHandler,
|
private val readReceiptHandler: ReadReceiptHandler,
|
||||||
private val settings: TimelineSettings,
|
private val settings: TimelineSettings,
|
||||||
@@ -101,6 +102,7 @@ internal class DefaultTimeline(
|
|||||||
eventDecryptor = eventDecryptor,
|
eventDecryptor = eventDecryptor,
|
||||||
paginationTask = paginationTask,
|
paginationTask = paginationTask,
|
||||||
realmConfiguration = realmConfiguration,
|
realmConfiguration = realmConfiguration,
|
||||||
|
cryptoRealmConfiguration = cryptoRealmConfiguration,
|
||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
fetchThreadTimelineTask = fetchThreadTimelineTask,
|
fetchThreadTimelineTask = fetchThreadTimelineTask,
|
||||||
getContextOfEventTask = getEventTask,
|
getContextOfEventTask = getEventTask,
|
||||||
|
@@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
@@ -29,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
|||||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
|
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
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.session.room.relation.threads.FetchThreadTimelineTask
|
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||||
@@ -40,6 +42,7 @@ import org.matrix.android.sdk.internal.util.time.Clock
|
|||||||
internal class DefaultTimelineService @AssistedInject constructor(
|
internal class DefaultTimelineService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
@CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration,
|
||||||
private val timelineInput: TimelineInput,
|
private val timelineInput: TimelineInput,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val eventDecryptor: TimelineEventDecryptor,
|
private val eventDecryptor: TimelineEventDecryptor,
|
||||||
@@ -68,6 +71,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
|||||||
initialEventId = eventId,
|
initialEventId = eventId,
|
||||||
settings = settings,
|
settings = settings,
|
||||||
realmConfiguration = monarchy.realmConfiguration,
|
realmConfiguration = monarchy.realmConfiguration,
|
||||||
|
cryptoRealmConfiguration = cryptoRealmConfiguration,
|
||||||
coroutineDispatchers = coroutineDispatchers,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
paginationTask = paginationTask,
|
paginationTask = paginationTask,
|
||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
|
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
|
||||||
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.internal.crypto.EventDecryptor
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
@@ -48,17 +46,7 @@ internal class DefaultGetEventTask @Inject constructor(
|
|||||||
|
|
||||||
// Try to decrypt the Event
|
// Try to decrypt the Event
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
tryOrNull(message = "Unable to decrypt the event") {
|
eventDecryptor.decryptAndUpdate(event, "")
|
||||||
eventDecryptor.decryptEvent(event, "")
|
|
||||||
}
|
|
||||||
?.let { result ->
|
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0)
|
event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0)
|
||||||
|
@@ -90,6 +90,7 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
data class Dependencies(
|
data class Dependencies(
|
||||||
val timelineSettings: TimelineSettings,
|
val timelineSettings: TimelineSettings,
|
||||||
val realm: AtomicReference<Realm>,
|
val realm: AtomicReference<Realm>,
|
||||||
|
val cryptoRealmConfiguration: RealmConfiguration,
|
||||||
val eventDecryptor: TimelineEventDecryptor,
|
val eventDecryptor: TimelineEventDecryptor,
|
||||||
val paginationTask: PaginationTask,
|
val paginationTask: PaginationTask,
|
||||||
val realmConfiguration: RealmConfiguration,
|
val realmConfiguration: RealmConfiguration,
|
||||||
@@ -127,7 +128,7 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val uiEchoManagerListener = object : UIEchoManager.Listener {
|
private val rebuildEventListener = object : RebuildListener {
|
||||||
override fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean {
|
override fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean {
|
||||||
return timelineChunk?.rebuildEvent(eventId, builder, searchInNext = true, searchInPrev = true).orFalse()
|
return timelineChunk?.rebuildEvent(eventId, builder, searchInNext = true, searchInPrev = true).orFalse()
|
||||||
}
|
}
|
||||||
@@ -161,7 +162,7 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
|
private val uiEchoManager = UIEchoManager(rebuildEventListener, clock)
|
||||||
private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
|
private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
realm = dependencies.realm,
|
realm = dependencies.realm,
|
||||||
@@ -176,11 +177,20 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
dependencies.matrixCoroutineDispatchers.main
|
dependencies.matrixCoroutineDispatchers.main
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val cryptoInfoEventDecorator = CryptoInfoEventDecorator(
|
||||||
|
realmConfiguration = dependencies.cryptoRealmConfiguration,
|
||||||
|
roomId = roomId,
|
||||||
|
clock = clock,
|
||||||
|
rebuildListener = rebuildEventListener,
|
||||||
|
onEventsUpdated = dependencies.onEventsUpdated
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun onStart() {
|
suspend fun onStart() {
|
||||||
dependencies.eventDecryptor.start()
|
dependencies.eventDecryptor.start()
|
||||||
dependencies.timelineInput.listeners.add(timelineInputListener)
|
dependencies.timelineInput.listeners.add(timelineInputListener)
|
||||||
val realm = dependencies.realm.get()
|
val realm = dependencies.realm.get()
|
||||||
sendingEventsDataSource.start()
|
sendingEventsDataSource.start()
|
||||||
|
cryptoInfoEventDecorator.start()
|
||||||
chunkEntity = getChunkEntity(realm).also {
|
chunkEntity = getChunkEntity(realm).also {
|
||||||
it.addChangeListener(chunkEntityListener)
|
it.addChangeListener(chunkEntityListener)
|
||||||
timelineChunk = it.createTimelineChunk()
|
timelineChunk = it.createTimelineChunk()
|
||||||
@@ -196,6 +206,7 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
dependencies.timelineInput.listeners.remove(timelineInputListener)
|
dependencies.timelineInput.listeners.remove(timelineInputListener)
|
||||||
chunkEntity?.removeChangeListener(chunkEntityListener)
|
chunkEntity?.removeChangeListener(chunkEntityListener)
|
||||||
sendingEventsDataSource.stop()
|
sendingEventsDataSource.stop()
|
||||||
|
cryptoInfoEventDecorator.stop()
|
||||||
timelineChunk?.close(closeNext = true, closePrev = true)
|
timelineChunk?.close(closeNext = true, closePrev = true)
|
||||||
getContextLatch?.cancel()
|
getContextLatch?.cancel()
|
||||||
chunkEntity = null
|
chunkEntity = null
|
||||||
@@ -336,6 +347,7 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
|
||||||
timelineEventMapper = dependencies.timelineEventMapper,
|
timelineEventMapper = dependencies.timelineEventMapper,
|
||||||
uiEchoManager = uiEchoManager,
|
uiEchoManager = uiEchoManager,
|
||||||
|
cryptoInfoEventDecorator = cryptoInfoEventDecorator,
|
||||||
threadsAwarenessHandler = dependencies.threadsAwarenessHandler,
|
threadsAwarenessHandler = dependencies.threadsAwarenessHandler,
|
||||||
lightweightSettingsStorage = dependencies.lightweightSettingsStorage,
|
lightweightSettingsStorage = dependencies.lightweightSettingsStorage,
|
||||||
initialEventId = mode.originEventId(),
|
initialEventId = mode.originEventId(),
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.session.room.timeline
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
interface RebuildListener {
|
||||||
|
fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean
|
||||||
|
}
|
@@ -61,6 +61,7 @@ internal class TimelineChunk(
|
|||||||
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val uiEchoManager: UIEchoManager?,
|
private val uiEchoManager: UIEchoManager?,
|
||||||
|
private val cryptoInfoEventDecorator: CryptoInfoEventDecorator?,
|
||||||
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
||||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||||
private val initialEventId: String?,
|
private val initialEventId: String?,
|
||||||
@@ -414,6 +415,8 @@ internal class TimelineChunk(
|
|||||||
).let {
|
).let {
|
||||||
// eventually enhance with ui echo?
|
// eventually enhance with ui echo?
|
||||||
(uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it)
|
(uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it)
|
||||||
|
}.let {
|
||||||
|
(cryptoInfoEventDecorator?.decorateTimelineEvent(it) ?: it)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -576,6 +579,7 @@ internal class TimelineChunk(
|
|||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
timelineEventMapper = timelineEventMapper,
|
timelineEventMapper = timelineEventMapper,
|
||||||
uiEchoManager = uiEchoManager,
|
uiEchoManager = uiEchoManager,
|
||||||
|
cryptoInfoEventDecorator = cryptoInfoEventDecorator,
|
||||||
threadsAwarenessHandler = threadsAwarenessHandler,
|
threadsAwarenessHandler = threadsAwarenessHandler,
|
||||||
lightweightSettingsStorage = lightweightSettingsStorage,
|
lightweightSettingsStorage = lightweightSettingsStorage,
|
||||||
initialEventId = null,
|
initialEventId = null,
|
||||||
|
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
|||||||
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.content.EncryptedEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
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.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
@@ -129,29 +130,30 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
threadAwareNonEncryptedEvents(request, realm)
|
threadAwareNonEncryptedEvents(request, realm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
||||||
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
when (val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }) {
|
||||||
val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }
|
is DecryptionResult.Success -> {
|
||||||
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
|
||||||
val eventEntity = EventEntity
|
val eventEntity = EventEntity
|
||||||
.where(it, eventId = eventId)
|
.where(it, eventId = eventId)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
eventEntity?.setDecryptionResult(result)
|
eventEntity?.setDecryptionResult(result.decryptedResult)
|
||||||
val decryptedEvent = eventEntity?.asDomain()
|
val decryptedEvent = eventEntity?.asDomain()
|
||||||
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity)
|
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: MXCryptoError) {
|
is DecryptionResult.Failure -> {
|
||||||
Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}")
|
val error = result.error
|
||||||
if (e is MXCryptoError.Base /*&& e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID*/) {
|
Timber.v("Failed to decrypt event ${event.eventId} : ${error.localizedMessage}")
|
||||||
// Keep track of unknown sessions to automatically try to decrypt on new session
|
|
||||||
realm.executeTransaction {
|
realm.executeTransaction {
|
||||||
EventEntity.where(it, eventId = event.eventId ?: "")
|
EventEntity.where(it, eventId = event.eventId ?: "")
|
||||||
.findFirst()
|
.findFirst()
|
||||||
?.let {
|
?.let {
|
||||||
it.decryptionErrorCode = e.errorType.name
|
it.decryptionErrorCode = (error as? MXCryptoError.Base)?.errorType?.name ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT.name
|
||||||
it.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
it.decryptionErrorReason = (error as? MXCryptoError.Base)?.technicalMessage ?: error.message
|
||||||
|
it.decryptionWithheldCode = result.withheldInfo?.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.content?.toModel<EncryptedEventContent>()?.let { content ->
|
event.content?.toModel<EncryptedEventContent>()?.let { content ->
|
||||||
@@ -163,12 +165,9 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
}
|
||||||
Timber.e("Failed to decrypt event ${event.eventId}, ${t.localizedMessage}")
|
synchronized(existingRequests) {
|
||||||
} finally {
|
existingRequests.remove(request)
|
||||||
synchronized(existingRequests) {
|
|
||||||
existingRequests.remove(request)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,14 +29,10 @@ import timber.log.Timber
|
|||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
|
||||||
internal class UIEchoManager(
|
internal class UIEchoManager(
|
||||||
private val listener: Listener,
|
private val listener: RebuildListener,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
private val inMemorySendingEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
private val inMemorySendingEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
||||||
|
|
||||||
fun getInMemorySendingEvents(): List<TimelineEvent> {
|
fun getInMemorySendingEvents(): List<TimelineEvent> {
|
||||||
|
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.sync.handler
|
|||||||
|
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
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.crypto.model.MXEventDecryptionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
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
|
||||||
@@ -28,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|||||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||||
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.DefaultCryptoService
|
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||||
|
import org.matrix.android.sdk.internal.crypto.algorithms.DecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
||||||
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
|
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -68,40 +68,33 @@ internal class CryptoSyncHandler @Inject constructor(
|
|||||||
* @param timelineId the timeline identifier
|
* @param timelineId the timeline identifier
|
||||||
* @return true if the event has been decrypted
|
* @return true if the event has been decrypted
|
||||||
*/
|
*/
|
||||||
private suspend fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
|
private suspend fun decryptToDeviceEvent(event: Event, timelineId: String?) {
|
||||||
Timber.v("## CRYPTO | decryptToDeviceEvent")
|
Timber.v("## CRYPTO | decryptToDeviceEvent")
|
||||||
if (event.getClearType() == EventType.ENCRYPTED) {
|
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||||
var result: MXEventDecryptionResult? = null
|
when (val result = cryptoService.decryptEvent(event, timelineId ?: "")) {
|
||||||
try {
|
is DecryptionResult.Success -> {
|
||||||
result = cryptoService.decryptEvent(event, timelineId ?: "")
|
val decryptionResult = result.decryptedResult
|
||||||
} catch (exception: MXCryptoError) {
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
|
payload = decryptionResult.clearEvent,
|
||||||
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
|
senderKey = decryptionResult.senderCurve25519Key,
|
||||||
// try to find device id to ease log reading
|
keysClaimed = decryptionResult.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
val deviceId = cryptoService.getCryptoDeviceInfo(event.senderId!!).firstOrNull {
|
forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
||||||
it.identityKey() == senderKey
|
)
|
||||||
}?.deviceId ?: senderKey
|
}
|
||||||
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
|
is DecryptionResult.Failure -> {
|
||||||
} catch (failure: Throwable) {
|
val exception = result.error
|
||||||
Timber.e(failure, "## CRYPTO | Failed to decrypt to device event from ${event.senderId}")
|
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType ?: MXCryptoError.ErrorType.UNABLE_TO_DECRYPT
|
||||||
}
|
event.mCryptoWithHeldCode = result.withheldInfo
|
||||||
|
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
|
||||||
if (null != result) {
|
// try to find device id to ease log reading
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
val deviceId = event.senderId?.let {
|
||||||
payload = result.clearEvent,
|
cryptoService.getCryptoDeviceInfo(it).firstOrNull {
|
||||||
senderKey = result.senderCurve25519Key,
|
it.identityKey() == senderKey
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
}?.deviceId
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
} ?: senderKey
|
||||||
)
|
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
|
||||||
return true
|
}
|
||||||
} else {
|
|
||||||
// Could happen for to device events
|
|
||||||
// None of the known session could decrypt the message
|
|
||||||
// In this case unwedging process might have been started (rate limited)
|
|
||||||
Timber.e("## CRYPTO | ERROR NULL DECRYPTION RESULT from ${event.senderId}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@ import io.realm.Realm
|
|||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
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
|
||||||
@@ -539,22 +538,11 @@ internal class RoomSyncHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
private fun decryptIfNeeded(event: Event, roomId: String) {
|
||||||
try {
|
if (event.mxDecryptionResult == null) {
|
||||||
val timelineId = generateTimelineId(roomId)
|
val timelineId = generateTimelineId(roomId)
|
||||||
// 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
|
||||||
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
||||||
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), timelineId) }
|
runBlocking { cryptoService.decryptAndUpdateEvent(event.copy(roomId = roomId), timelineId) }
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
} catch (e: MXCryptoError) {
|
|
||||||
if (e is MXCryptoError.Base) {
|
|
||||||
event.mCryptoError = e.errorType
|
|
||||||
event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -146,8 +146,7 @@ class DecryptionFailureTracker @Inject constructor(
|
|||||||
private fun MXCryptoError.ErrorType.toAnalyticsErrorName(): DetailedErrorName {
|
private fun MXCryptoError.ErrorType.toAnalyticsErrorName(): DetailedErrorName {
|
||||||
val detailed = "$name | mxc_crypto_error_type"
|
val detailed = "$name | mxc_crypto_error_type"
|
||||||
val errorName = when (this) {
|
val errorName = when (this) {
|
||||||
MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID,
|
MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID -> Error.Name.OlmKeysNotSentError
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD -> Error.Name.OlmKeysNotSentError
|
|
||||||
MXCryptoError.ErrorType.OLM -> Error.Name.OlmUnspecifiedError
|
MXCryptoError.ErrorType.OLM -> Error.Name.OlmUnspecifiedError
|
||||||
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX -> Error.Name.OlmIndexError
|
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX -> Error.Name.OlmIndexError
|
||||||
else -> Error.Name.UnknownError
|
else -> Error.Name.UnknownError
|
||||||
|
@@ -80,11 +80,9 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
|||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|
||||||
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.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
||||||
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
@@ -1111,13 +1109,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
private fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) {
|
private fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) {
|
||||||
room.getTimelineEvent(action.eventId)?.let {
|
room.getTimelineEvent(action.eventId)?.let {
|
||||||
val code = when (it.root.mCryptoError) {
|
val code = it.root.mCryptoWithHeldCode
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD -> {
|
|
||||||
WithHeldCode.fromCode(it.root.mCryptoErrorReason)
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowE2EErrorMessage(code))
|
_viewEvents.post(RoomDetailViewEvents.ShowE2EErrorMessage(code))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -420,6 +420,11 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
addViewSourceItems(timelineEvent)
|
addViewSourceItems(timelineEvent)
|
||||||
|
} else {
|
||||||
|
if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1 ||
|
||||||
|
timelineEvent.senderInfo.userId != session.myUserId) {
|
||||||
|
add(EventSharedAction.ReRequestKey(timelineEvent.eventId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
add(EventSharedAction.CopyPermalink(eventId))
|
add(EventSharedAction.CopyPermalink(eventId))
|
||||||
if (session.myUserId != timelineEvent.root.senderId) {
|
if (session.myUserId != timelineEvent.root.senderId) {
|
||||||
|
@@ -29,11 +29,8 @@ import im.vector.app.core.platform.EmptyViewEvents
|
|||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import kotlinx.coroutines.launch
|
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.MXCryptoError
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.isReply
|
import org.matrix.android.sdk.api.session.events.model.isReply
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class ViewEditHistoryViewModel @AssistedInject constructor(
|
class ViewEditHistoryViewModel @AssistedInject constructor(
|
||||||
@@ -76,18 +73,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(
|
|||||||
val timelineID = event.roomId + UUID.randomUUID().toString()
|
val timelineID = event.roomId + UUID.randomUUID().toString()
|
||||||
// We need to check encryption
|
// We need to check encryption
|
||||||
if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
||||||
// for now decrypt sync
|
session.cryptoService().decryptAndUpdateEvent(event, timelineID)
|
||||||
try {
|
|
||||||
val result = session.cryptoService().decryptEvent(event, timelineID)
|
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
} catch (e: MXCryptoError) {
|
|
||||||
Timber.w("Failed to decrypt event in history")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.eventId == eventId) {
|
if (event.eventId == eventId) {
|
||||||
|
@@ -30,9 +30,9 @@ import im.vector.app.features.settings.VectorPreferences
|
|||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
import me.gujun.android.span.image
|
import me.gujun.android.span.image
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|
||||||
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.content.EncryptedEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -56,16 +56,18 @@ class EncryptedItemFactory @Inject constructor(
|
|||||||
val cryptoError = event.root.mCryptoError
|
val cryptoError = event.root.mCryptoError
|
||||||
|
|
||||||
val spannableStr = if (vectorPreferences.developerMode()) {
|
val spannableStr = if (vectorPreferences.developerMode()) {
|
||||||
val errorDescription =
|
// In developer mode we want to show the raw error
|
||||||
if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
val errorDescription = if (event.root.mCryptoWithHeldCode != null) {
|
||||||
stringProvider.getString(R.string.notice_crypto_error_unknown_inbound_session_id)
|
"${cryptoError?.name ?: "NULL"} | ${event.root.mCryptoWithHeldCode?.value}"
|
||||||
} else {
|
} else {
|
||||||
// TODO i18n
|
cryptoError?.name ?: "NULL"
|
||||||
cryptoError?.name
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val message = stringProvider.getString(R.string.encrypted_message).takeIf { cryptoError == null }
|
val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription).let {
|
||||||
?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
if (event.hasActiveRequestForKeys == true) {
|
||||||
|
"$it - key request in progress"
|
||||||
|
} else it
|
||||||
|
}
|
||||||
span(message) {
|
span(message) {
|
||||||
textStyle = "italic"
|
textStyle = "italic"
|
||||||
textColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
textColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||||
@@ -78,29 +80,38 @@ class EncryptedItemFactory @Inject constructor(
|
|||||||
textColor = colorFromAttribute
|
textColor = colorFromAttribute
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when (cryptoError) {
|
if (event.hasActiveRequestForKeys == true) {
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD -> {
|
span {
|
||||||
span {
|
drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let {
|
||||||
drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let {
|
image(it, "baseline")
|
||||||
image(it, "baseline")
|
+" "
|
||||||
+" "
|
}
|
||||||
}
|
span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_requesting_keys)) {
|
||||||
span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_final)) {
|
textStyle = "italic"
|
||||||
textStyle = "italic"
|
textColor = colorFromAttribute
|
||||||
textColor = colorFromAttribute
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
} else if (event.root.mCryptoWithHeldCode != null) {
|
||||||
span {
|
val messageId = when (event.root.mCryptoWithHeldCode) {
|
||||||
drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let {
|
WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted
|
||||||
image(it, "baseline")
|
WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified
|
||||||
+" "
|
else -> R.string.crypto_error_withheld_generic
|
||||||
}
|
}
|
||||||
span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_friendly)) {
|
span {
|
||||||
textStyle = "italic"
|
drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let {
|
||||||
textColor = colorFromAttribute
|
image(it, "baseline")
|
||||||
}
|
+" "
|
||||||
|
}
|
||||||
|
span(stringProvider.getString(messageId)) {
|
||||||
|
textStyle = "italic"
|
||||||
|
textColor = colorFromAttribute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
span {
|
||||||
|
span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_no_keys)) {
|
||||||
|
textStyle = "italic"
|
||||||
|
textColor = colorFromAttribute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,8 +27,6 @@ import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormat
|
|||||||
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.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
|
||||||
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.isEdition
|
import org.matrix.android.sdk.api.session.events.model.isEdition
|
||||||
@@ -48,7 +46,6 @@ import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId
|
|||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,16 +204,7 @@ class NotifiableEventResolver @Inject constructor(
|
|||||||
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
|
||||||
try {
|
session.cryptoService().decryptAndUpdateEvent(root, "")
|
||||||
val result = session.cryptoService().decryptEvent(root, root.roomId + UUID.randomUUID().toString())
|
|
||||||
root.mxDecryptionResult = OlmDecryptionResult(
|
|
||||||
payload = result.clearEvent,
|
|
||||||
senderKey = result.senderCurve25519Key,
|
|
||||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
|
||||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
|
||||||
)
|
|
||||||
} catch (ignore: MXCryptoError) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -125,7 +125,8 @@
|
|||||||
<string name="notice_power_level_diff">%1$s from %2$s to %3$s</string>
|
<string name="notice_power_level_diff">%1$s from %2$s to %3$s</string>
|
||||||
|
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
||||||
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
|
<string name="notice_crypto_error_unknown_inbound_session_id" tools:ignore="UnusedResources">The sender\'s device has not sent us the keys for this message.</string>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
|
|
||||||
@@ -2673,8 +2674,11 @@
|
|||||||
<string name="room_settings_save_success">You changed room settings successfully</string>
|
<string name="room_settings_save_success">You changed room settings successfully</string>
|
||||||
<string name="room_settings_set_avatar">Set avatar</string>
|
<string name="room_settings_set_avatar">Set avatar</string>
|
||||||
|
|
||||||
<string name="notice_crypto_unable_to_decrypt_final">You cannot access this message</string>
|
<string name="notice_crypto_unable_to_decrypt_no_keys">"Unable to decrypt message, the sender's device has not sent us the keys."</string>
|
||||||
|
<!-- TODO TO BE REMOVED -->
|
||||||
|
<string name="notice_crypto_unable_to_decrypt_final" tools:ignore="UnusedResources">You cannot access this message</string>
|
||||||
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>
|
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>
|
||||||
|
<string name="notice_crypto_unable_to_decrypt_requesting_keys">"Requesting keys to decrypt this message, this may take a while"</string>
|
||||||
<string name="notice_crypto_unable_to_decrypt_friendly_desc">Due to end-to-end encryption, you might need to wait for someone\'s message to arrive because the encryption keys were not properly sent to you.</string>
|
<string name="notice_crypto_unable_to_decrypt_friendly_desc">Due to end-to-end encryption, you might need to wait for someone\'s message to arrive because the encryption keys were not properly sent to you.</string>
|
||||||
<string name="crypto_error_withheld_blacklisted">You cannot access this message because you have been blocked by the sender</string>
|
<string name="crypto_error_withheld_blacklisted">You cannot access this message because you have been blocked by the sender</string>
|
||||||
<string name="crypto_error_withheld_unverified">You cannot access this message because your session is not trusted by the sender</string>
|
<string name="crypto_error_withheld_unverified">You cannot access this message because your session is not trusted by the sender</string>
|
||||||
|
Reference in New Issue
Block a user