,
direction: PaginationDirection) {
events.forEach { event ->
- val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
+ val fakeEvent = event.toEntity(roomId, SendState.SYNCED, clock.epochMillis()).let {
realm.copyToRealm(it)
}
addTimelineEvent(
roomId = roomId,
eventEntity = fakeEvent,
direction = direction,
- roomMemberContentsByUser = emptyMap())
+ roomMemberContentsByUser = emptyMap()
+ )
}
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
index b86c86c0c7..ccf1c7c2c9 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
@@ -26,8 +26,8 @@ internal class FakeGetContextOfEventTask constructor(private val tokenChunkEvent
override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
val tokenChunkEvent = FakeTokenChunkEvent(
- Random.nextLong(System.currentTimeMillis()).toString(),
- Random.nextLong(System.currentTimeMillis()).toString(),
+ Random.nextLong().toString(),
+ Random.nextLong().toString(),
fakeEvents
)
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
index d09bfb18c6..f241be0c5c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
@@ -25,7 +25,7 @@ internal class FakePaginationTask @Inject constructor(private val tokenChunkEven
override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
- val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
+ val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong().toString(), fakeEvents)
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
}
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
index 3864ea1cd1..d5b4a07fc0 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
@@ -71,7 +71,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
val sentMessages = commonTestHelper.sendTextMessage(
roomFromAlicePOV,
message,
- numberOfMessagesToSend)
+ numberOfMessagesToSend
+ )
// Alice clear the cache and restart the sync
commonTestHelper.clearCacheAndSync(aliceSession)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
index 5d09b74e6c..6e5fed8df9 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
@@ -94,7 +94,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
val firstMessageFromAliceId = commonTestHelper.sendTextMessage(
roomFromAlicePOV,
firstMessage,
- 30)
+ 30
+ )
.last()
.eventId
@@ -130,7 +131,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
commonTestHelper.sendTextMessage(
roomFromAlicePOV,
secondMessage,
- 30)
+ 30
+ )
// Bob start to sync
bobSession.startSync(true)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
index 251b2c614c..42f710d7cf 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
@@ -64,7 +64,8 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
commonTestHelper.sendTextMessage(
roomFromAlicePOV,
message,
- numberOfMessagesToSent)
+ numberOfMessagesToSent
+ )
val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
bobTimeline.start()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
index ab0bbe7f73..e17b7efbd6 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
@@ -85,7 +85,8 @@ class SearchMessagesTest : InstrumentedTest {
commonTestHelper.sendTextMessage(
roomFromAlicePOV,
MESSAGE,
- 2)
+ 2
+ )
val data = commonTestHelper.runBlockingTest {
block.invoke(cryptoTestData)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
index d2c8b52fc7..6a17cb74ad 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -177,21 +177,27 @@ class SpaceHierarchyTest : InstrumentedTest {
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
- val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ val spaceAInfo = createPublicSpace(
+ session, "SpaceA", listOf(
Triple("A1", true /*auto-join*/, true/*canonical*/),
Triple("A2", true, true)
- ))
+ )
+ )
- /* val spaceBInfo = */ createPublicSpace(session, "SpaceB", listOf(
+ /* val spaceBInfo = */ createPublicSpace(
+ session, "SpaceB", listOf(
Triple("B1", true /*auto-join*/, true/*canonical*/),
Triple("B2", true, true),
Triple("B3", true, true)
- ))
+ )
+ )
- val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ val spaceCInfo = createPublicSpace(
+ session, "SpaceC", listOf(
Triple("C1", true /*auto-join*/, true/*canonical*/),
Triple("C2", true, true)
- ))
+ )
+ )
// add C as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
@@ -254,15 +260,19 @@ class SpaceHierarchyTest : InstrumentedTest {
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
- val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ val spaceAInfo = createPublicSpace(
+ session, "SpaceA", listOf(
Triple("A1", true /*auto-join*/, true/*canonical*/),
Triple("A2", true, true)
- ))
+ )
+ )
- val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ val spaceCInfo = createPublicSpace(
+ session, "SpaceC", listOf(
Triple("C1", true /*auto-join*/, true/*canonical*/),
Triple("C2", true, true)
- ))
+ )
+ )
// add C as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
@@ -296,16 +306,20 @@ class SpaceHierarchyTest : InstrumentedTest {
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
- val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ val spaceAInfo = createPublicSpace(
+ session, "SpaceA", listOf(
Triple("A1", true /*auto-join*/, true/*canonical*/),
Triple("A2", true, true)
- ))
+ )
+ )
- val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ val spaceBInfo = createPublicSpace(
+ session, "SpaceB", listOf(
Triple("B1", true /*auto-join*/, true/*canonical*/),
Triple("B2", true, true),
Triple("B3", true, true)
- ))
+ )
+ )
// add B as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
@@ -315,10 +329,12 @@ class SpaceHierarchyTest : InstrumentedTest {
session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
}
- val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ val spaceCInfo = createPublicSpace(
+ session, "SpaceC", listOf(
Triple("C1", true /*auto-join*/, true/*canonical*/),
Triple("C2", true, true)
- ))
+ )
+ )
commonTestHelper.waitWithLatch { latch ->
@@ -446,21 +462,27 @@ class SpaceHierarchyTest : InstrumentedTest {
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
- /* val spaceAInfo = */ createPublicSpace(session, "SpaceA", listOf(
+ /* val spaceAInfo = */ createPublicSpace(
+ session, "SpaceA", listOf(
Triple("A1", true /*auto-join*/, true/*canonical*/),
Triple("A2", true, true)
- ))
+ )
+ )
- val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ val spaceBInfo = createPublicSpace(
+ session, "SpaceB", listOf(
Triple("B1", true /*auto-join*/, true/*canonical*/),
Triple("B2", true, true),
Triple("B3", true, true)
- ))
+ )
+ )
- val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ val spaceCInfo = createPublicSpace(
+ session, "SpaceC", listOf(
Triple("C1", true /*auto-join*/, true/*canonical*/),
Triple("C2", true, true)
- ))
+ )
+ )
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
@@ -494,10 +516,12 @@ class SpaceHierarchyTest : InstrumentedTest {
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
- val spaceAInfo = createPrivateSpace(aliceSession, "Private Space A", listOf(
+ val spaceAInfo = createPrivateSpace(
+ aliceSession, "Private Space A", listOf(
Triple("General", true /*suggested*/, true/*canonical*/),
Triple("Random", true, true)
- ))
+ )
+ )
commonTestHelper.runBlockingTest {
aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt
index 0efecbbe8a..83fcae7190 100644
--- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt
+++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt
@@ -28,8 +28,12 @@ internal class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContex
val display = node.javaClass == DisplayMaths::class.java
val contents = node.firstChild // should be the only child
val latex = (contents as Text).literal
- val attributes = context.extendAttributes(node, if (display) "div" else "span", Collections.singletonMap("data-mx-maths",
- latex))
+ val attributes = context.extendAttributes(
+ node, if (display) "div" else "span", Collections.singletonMap(
+ "data-mx-maths",
+ latex
+ )
+ )
html.tag(if (display) "div" else "span", attributes)
html.tag("code")
context.render(contents)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 217f7e3da8..e7d1e64a2b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -132,9 +132,11 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
instance = Matrix(appContext, matrixConfiguration)
} else {
- throw IllegalStateException("Matrix is not initialized properly." +
- " If you want to manage your own Matrix instance use Matrix.createInstance" +
- " otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider.")
+ throw IllegalStateException(
+ "Matrix is not initialized properly." +
+ " If you want to manage your own Matrix instance use Matrix.createInstance" +
+ " otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider."
+ )
}
}
return instance
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
index 1a4c1ee51c..80630bc4e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
@@ -1,127 +1,129 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.api.auth
-
-import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
-import org.matrix.android.sdk.api.auth.registration.TermPolicies
-
-/**
- * This method extract the policies from the login terms parameter, regarding the user language.
- * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
- *
- * Example of Data:
- *
- * "m.login.terms": {
- * "policies": {
- * "privacy_policy": {
- * "version": "1.0",
- * "en": {
- * "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
- * "name": "Terms and Conditions"
- * }
- * }
- * }
- * }
- *
- *
- * @param userLanguage the user language
- * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
- */
-fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
- defaultLanguage: String = "en"): List {
- val result = ArrayList()
-
- val policies = get("policies")
- if (policies is Map<*, *>) {
- policies.keys.forEach { policyName ->
- val localizedFlowDataLoginTermsPolicyName = policyName as String
- var localizedFlowDataLoginTermsVersion: String? = null
- var localizedFlowDataLoginTermsLocalizedUrl: String? = null
- var localizedFlowDataLoginTermsLocalizedName: String? = null
-
- val policy = policies[policyName]
-
- // Enter this policy
- if (policy is Map<*, *>) {
- // Version
- localizedFlowDataLoginTermsVersion = policy["version"] as String?
-
- var userLanguageUrlAndName: UrlAndName? = null
- var defaultLanguageUrlAndName: UrlAndName? = null
- var firstUrlAndName: UrlAndName? = null
-
- // Search for language
- policy.keys.forEach { policyKey ->
- when (policyKey) {
- "version" -> Unit // Ignore
- userLanguage -> {
- // We found the data for the user language
- userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
- }
- defaultLanguage -> {
- // We found default language
- defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
- }
- else -> {
- if (firstUrlAndName == null) {
- // Get at least some data
- firstUrlAndName = extractUrlAndName(policy[policyKey])
- }
- }
- }
- }
-
- // Copy found language data by priority
- when {
- userLanguageUrlAndName != null -> {
- localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url
- localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name
- }
- defaultLanguageUrlAndName != null -> {
- localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url
- localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name
- }
- firstUrlAndName != null -> {
- localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url
- localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name
- }
- }
- }
-
- result.add(LocalizedFlowDataLoginTerms(
- policyName = localizedFlowDataLoginTermsPolicyName,
- version = localizedFlowDataLoginTermsVersion,
- localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
- localizedName = localizedFlowDataLoginTermsLocalizedName
- ))
- }
- }
-
- return result
-}
-
-private fun extractUrlAndName(policyData: Any?): UrlAndName? {
- if (policyData is Map<*, *>) {
- val url = policyData["url"] as String?
- val name = policyData["name"] as String?
-
- if (url != null && name != null) {
- return UrlAndName(url, name)
- }
- }
- return null
-}
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.auth
+
+import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
+import org.matrix.android.sdk.api.auth.registration.TermPolicies
+
+/**
+ * This method extract the policies from the login terms parameter, regarding the user language.
+ * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
+ *
+ * Example of Data:
+ *
+ * "m.login.terms": {
+ * "policies": {
+ * "privacy_policy": {
+ * "version": "1.0",
+ * "en": {
+ * "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
+ * "name": "Terms and Conditions"
+ * }
+ * }
+ * }
+ * }
+ *
+ *
+ * @param userLanguage the user language
+ * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
+ */
+fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
+ defaultLanguage: String = "en"): List {
+ val result = ArrayList()
+
+ val policies = get("policies")
+ if (policies is Map<*, *>) {
+ policies.keys.forEach { policyName ->
+ val localizedFlowDataLoginTermsPolicyName = policyName as String
+ var localizedFlowDataLoginTermsVersion: String? = null
+ var localizedFlowDataLoginTermsLocalizedUrl: String? = null
+ var localizedFlowDataLoginTermsLocalizedName: String? = null
+
+ val policy = policies[policyName]
+
+ // Enter this policy
+ if (policy is Map<*, *>) {
+ // Version
+ localizedFlowDataLoginTermsVersion = policy["version"] as String?
+
+ var userLanguageUrlAndName: UrlAndName? = null
+ var defaultLanguageUrlAndName: UrlAndName? = null
+ var firstUrlAndName: UrlAndName? = null
+
+ // Search for language
+ policy.keys.forEach { policyKey ->
+ when (policyKey) {
+ "version" -> Unit // Ignore
+ userLanguage -> {
+ // We found the data for the user language
+ userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
+ }
+ defaultLanguage -> {
+ // We found default language
+ defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
+ }
+ else -> {
+ if (firstUrlAndName == null) {
+ // Get at least some data
+ firstUrlAndName = extractUrlAndName(policy[policyKey])
+ }
+ }
+ }
+ }
+
+ // Copy found language data by priority
+ when {
+ userLanguageUrlAndName != null -> {
+ localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url
+ localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name
+ }
+ defaultLanguageUrlAndName != null -> {
+ localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url
+ localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name
+ }
+ firstUrlAndName != null -> {
+ localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url
+ localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name
+ }
+ }
+ }
+
+ result.add(
+ LocalizedFlowDataLoginTerms(
+ policyName = localizedFlowDataLoginTermsPolicyName,
+ version = localizedFlowDataLoginTermsVersion,
+ localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
+ localizedName = localizedFlowDataLoginTermsLocalizedName
+ )
+ )
+ }
+ }
+
+ return result
+}
+
+private fun extractUrlAndName(policyData: Any?): UrlAndName? {
+ if (policyData is Map<*, *>) {
+ val url = policyData["url"] as String?
+ val name = policyData["name"] as String?
+
+ if (url != null && name != null) {
+ return UrlAndName(url, name)
+ }
+ }
+ return null
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
index 0da9eb4b7e..2b421f2873 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
@@ -88,8 +88,10 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
val isMandatory = flows?.all { type in it.stages.orEmpty() } == true
val stage = when (type) {
- LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
- ?: "")
+ LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(
+ isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
+ ?: ""
+ )
LoginFlowTypes.DUMMY -> Stage.Dummy(isMandatory)
LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap())
LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt
index 9a686de2e1..9507ddda65 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt
@@ -31,5 +31,11 @@ data class MXCryptoConfig constructor(
* If set to false, the request will be forwarded to the application layer; in this
* case the application can decide to prompt the user.
*/
- val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
+ val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true,
+
+ /**
+ * Currently megolm keys are requested to the sender device and to all of our devices.
+ * You can limit request only to your sessions by turning this setting to `true`
+ */
+ val limitRoomKeyRequestsToMyDevices: Boolean = false,
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
index d6d1248de7..b8c08d23dc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningServic
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
+import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
@@ -35,8 +36,6 @@ 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.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
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.Event
@@ -76,6 +75,15 @@ interface CryptoService {
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
+ /**
+ * Enable or disable key gossiping.
+ * Default is true.
+ * If set to false this device won't send key_request nor will accept key forwarded
+ */
+ fun enableKeyGossiping(enable: Boolean)
+
+ fun isKeyGossipingEnabled(): Boolean
+
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
fun getDeviceTrackingStatus(userId: String): Int
@@ -94,8 +102,6 @@ interface CryptoService {
fun reRequestRoomKeyForEvent(event: Event)
- fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
-
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
@@ -142,14 +148,20 @@ interface CryptoService {
fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener)
- fun getOutgoingRoomKeyRequests(): List
- fun getOutgoingRoomKeyRequestsPaged(): LiveData>
+ fun getOutgoingRoomKeyRequests(): List
+ fun getOutgoingRoomKeyRequestsPaged(): LiveData>
fun getIncomingRoomKeyRequests(): List
fun getIncomingRoomKeyRequestsPaged(): LiveData>
- fun getGossipingEventsTrail(): LiveData>
- fun getGossipingEvents(): List
+ /**
+ * Can be called by the app layer to accept a request manually
+ * Use carefully as it is prone to social attacks
+ */
+ suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest)
+
+ fun getGossipingEventsTrail(): LiveData>
+ fun getGossipingEvents(): List
// For testing shared session
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt
new file mode 100644
index 0000000000..855f17a34f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.api.session.crypto
+
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
+
+data class RequestReply(
+ val userId: String,
+ val fromDevice: String?,
+ val result: RequestResult
+)
+
+sealed class RequestResult {
+ data class Success(val chainIndex: Int) : RequestResult()
+ data class Failure(val code: WithHeldCode) : RequestResult()
+}
+
+data class OutgoingKeyRequest(
+ var requestBody: RoomKeyRequestBody?,
+ // recipients for the request map of users to list of deviceId
+ val recipients: Map>,
+ val fromIndex: Int,
+ // Unique id for this request. Used for both
+ // an id within the request for later pairing with a cancellation, and for
+ // the transaction id when sending the to_device messages to our local
+ val requestId: String, // current state of this request
+ val state: OutgoingRoomKeyRequestState,
+ val results: List
+) {
+ /**
+ * Used only for log.
+ *
+ * @return the room id.
+ */
+ val roomId = requestBody?.roomId
+
+ /**
+ * Used only for log.
+ *
+ * @return the session id
+ */
+ val sessionId = requestBody?.sessionId
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingRoomKeyRequestState.kt
similarity index 55%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingRoomKeyRequestState.kt
index 2438e01102..6e80bdc133 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingRoomKeyRequestState.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,20 @@
* limitations under the License.
*/
-package org.matrix.android.sdk.internal.crypto
+package org.matrix.android.sdk.api.session.crypto
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
+enum class OutgoingRoomKeyRequestState {
+ UNSENT,
+ SENT,
+ SENT_THEN_CANCELED,
+ CANCELLATION_PENDING,
+ CANCELLATION_PENDING_AND_WILL_RESEND;
-internal interface OutgoingGossipingRequest {
- val recipients: Map>
- val requestId: String
- val state: OutgoingGossipingRequestState
- // transaction id for the cancellation, if any
- // var cancellationTxnId: String?
+ companion object {
+ fun pendingStates() = setOf(
+ UNSENT,
+ CANCELLATION_PENDING_AND_WILL_RESEND,
+ CANCELLATION_PENDING
+ )
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt
index 11996e673e..70ff76a4ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt
@@ -93,7 +93,8 @@ data class CryptoCrossSigningKey(
userId = userId,
usages = listOf(usage.value),
keys = mapOf("ed25519:$b64key" to b64key),
- signatures = signMap)
+ signatures = signMap
+ )
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt
index 3cd36c2ce8..24d3cf4004 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt
@@ -16,9 +16,8 @@
package org.matrix.android.sdk.api.session.crypto.keyshare
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
+import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
/**
* Room keys events listener
@@ -35,12 +34,12 @@ interface GossipingRequestListener {
* Returns the secret value to be shared
* @return true if is handled
*/
- fun onSecretShareRequest(request: IncomingSecretShareRequest): Boolean
+ fun onSecretShareRequest(request: SecretShareRequest): Boolean
/**
* A room key request cancellation has been received.
*
* @param request the cancellation request
*/
- fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation)
+ fun onRequestCancelled(request: IncomingRoomKeyRequest)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/AuditTrail.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/AuditTrail.kt
new file mode 100644
index 0000000000..861f3bd30b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/AuditTrail.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.api.session.crypto.model
+
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
+
+enum class TrailType {
+ OutgoingKeyForward,
+ IncomingKeyForward,
+ OutgoingKeyWithheld,
+ IncomingKeyRequest,
+ Unknown
+}
+
+interface AuditInfo {
+ val roomId: String
+ val sessionId: String
+ val senderKey: String
+ val alg: String
+ val userId: String
+ val deviceId: String
+}
+
+@JsonClass(generateAdapter = true)
+data class ForwardInfo(
+ override val roomId: String,
+ override val sessionId: String,
+ override val senderKey: String,
+ override val alg: String,
+ override val userId: String,
+ override val deviceId: String,
+ val chainIndex: Long?
+) : AuditInfo
+
+object UnknownInfo : AuditInfo {
+ override val roomId: String = ""
+ override val sessionId: String = ""
+ override val senderKey: String = ""
+ override val alg: String = ""
+ override val userId: String = ""
+ override val deviceId: String = ""
+}
+
+@JsonClass(generateAdapter = true)
+data class WithheldInfo(
+ override val roomId: String,
+ override val sessionId: String,
+ override val senderKey: String,
+ override val alg: String,
+ val code: WithHeldCode,
+ override val userId: String,
+ override val deviceId: String
+) : AuditInfo
+
+@JsonClass(generateAdapter = true)
+data class IncomingKeyRequestInfo(
+ override val roomId: String,
+ override val sessionId: String,
+ override val senderKey: String,
+ override val alg: String,
+ override val userId: String,
+ override val deviceId: String,
+ val requestId: String
+) : AuditInfo
+
+data class AuditTrail(
+ val ageLocalTs: Long,
+ val type: TrailType,
+ val info: AuditInfo
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
deleted file mode 100755
index 74ca7304f7..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.api.session.crypto.model
-
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
-import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
-
-/**
- * IncomingRequestCancellation describes the incoming room key cancellation.
- */
-data class IncomingRequestCancellation(
- /**
- * The user id
- */
- override val userId: String? = null,
-
- /**
- * The device id
- */
- override val deviceId: String? = null,
-
- /**
- * The request id
- */
- override val requestId: String? = null,
- override val localCreationTimestamp: Long?
-) : IncomingShareRequestCommon {
- companion object {
- /**
- * Factory
- *
- * @param event the event
- */
- fun fromEvent(event: Event): IncomingRequestCancellation? {
- return event.getClearContent()
- .toModel()
- ?.let {
- IncomingRequestCancellation(
- userId = event.senderId,
- deviceId = it.requestingDeviceId,
- requestId = it.requestId,
- localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
- )
- }
- }
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
index 45b0926d89..0a28478a10 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
@@ -16,9 +16,7 @@
package org.matrix.android.sdk.api.session.crypto.model
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
+import org.matrix.android.sdk.internal.util.time.Clock
/**
* IncomingRoomKeyRequest class defines the incoming room keys request.
@@ -27,56 +25,62 @@ data class IncomingRoomKeyRequest(
/**
* The user id
*/
- override val userId: String? = null,
+ val userId: String? = null,
/**
* The device id
*/
- override val deviceId: String? = null,
+ val deviceId: String? = null,
/**
* The request id
*/
- override val requestId: String? = null,
+ val requestId: String? = null,
/**
* The request body
*/
val requestBody: RoomKeyRequestBody? = null,
- val state: GossipingRequestState = GossipingRequestState.NONE,
-
- /**
- * The runnable to call to accept to share the keys
- */
- @Transient
- var share: Runnable? = null,
-
- /**
- * The runnable to call to ignore the key share request.
- */
- @Transient
- var ignore: Runnable? = null,
- override val localCreationTimestamp: Long?
-) : IncomingShareRequestCommon {
+ val localCreationTimestamp: Long?
+) {
companion object {
/**
* Factory
*
* @param event the event
+ * @param currentTimeMillis the current time in milliseconds
*/
- fun fromEvent(event: Event): IncomingRoomKeyRequest? {
- return event.getClearContent()
- .toModel()
+ fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? {
+ return trail
+ .takeIf { it.type == TrailType.IncomingKeyRequest }
+ ?.let {
+ it.info as? IncomingKeyRequestInfo
+ }
?.let {
IncomingRoomKeyRequest(
- userId = event.senderId,
- deviceId = it.requestingDeviceId,
+ userId = it.userId,
+ deviceId = it.deviceId,
requestId = it.requestId,
- requestBody = it.body ?: RoomKeyRequestBody(),
- localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+ requestBody = RoomKeyRequestBody(
+ algorithm = it.alg,
+ roomId = it.roomId,
+ senderKey = it.senderKey,
+ sessionId = it.sessionId
+ ),
+ localCreationTimestamp = trail.ageLocalTs
)
}
}
+
+ internal fun fromRestRequest(senderId: String, request: RoomKeyShareRequest, clock: Clock): IncomingRoomKeyRequest? {
+ return IncomingRoomKeyRequest(
+ userId = senderId,
+ deviceId = request.requestingDeviceId,
+ requestId = request.requestId,
+ requestBody = request.body,
+ localCreationTimestamp = clock.epochMillis()
+ )
+ }
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
deleted file mode 100755
index 5afffef1ae..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.api.session.crypto.model
-
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
-
-/**
- * IncomingSecretShareRequest class defines the incoming secret keys request.
- */
-data class IncomingSecretShareRequest(
- /**
- * The user id
- */
- override val userId: String? = null,
-
- /**
- * The device id
- */
- override val deviceId: String? = null,
-
- /**
- * The request id
- */
- override val requestId: String? = null,
-
- /**
- * The request body
- */
- val secretName: String? = null,
-
- /**
- * The runnable to call to accept to share the keys
- */
- @Transient
- var share: ((String) -> Unit)? = null,
-
- /**
- * The runnable to call to ignore the key share request.
- */
- @Transient
- var ignore: Runnable? = null,
-
- override val localCreationTimestamp: Long?
-
-) : IncomingShareRequestCommon {
- companion object {
- /**
- * Factory
- *
- * @param event the event
- */
- fun fromEvent(event: Event): IncomingSecretShareRequest? {
- return event.getClearContent()
- .toModel()
- ?.let {
- IncomingSecretShareRequest(
- userId = event.senderId,
- deviceId = it.requestingDeviceId,
- requestId = it.requestId,
- secretName = it.secretName,
- localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
- )
- }
- }
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt
deleted file mode 100755
index 5f35cc908f..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.api.session.crypto.model
-
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest
-
-/**
- * Represents an outgoing room key request
- */
-@JsonClass(generateAdapter = true)
-data class OutgoingRoomKeyRequest(
- // RequestBody
- val requestBody: RoomKeyRequestBody?,
- // list of recipients for the request
- override val recipients: Map>,
- // Unique id for this request. Used for both
- // an id within the request for later pairing with a cancellation, and for
- // the transaction id when sending the to_device messages to our local
- override val requestId: String, // current state of this request
- override val state: OutgoingGossipingRequestState
- // transaction id for the cancellation, if any
- // override var cancellationTxnId: String? = null
-) : OutgoingGossipingRequest {
-
- /**
- * Used only for log.
- *
- * @return the room id.
- */
- val roomId: String?
- get() = requestBody?.roomId
-
- /**
- * Used only for log.
- *
- * @return the session id
- */
- val sessionId: String?
- get() = requestBody?.sessionId
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
index b9d0c0ad2c..ec67e4b31d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
@@ -129,11 +129,10 @@ interface VerificationService {
private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000
private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
- fun isValidRequest(age: Long?): Boolean {
+ fun isValidRequest(age: Long?, currentTimeMillis: Long): Boolean {
if (age == null) return false
- val now = System.currentTimeMillis()
- val tooInThePast = now - TEN_MINUTES_IN_MILLIS
- val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS
+ val tooInThePast = currentTimeMillis - TEN_MINUTES_IN_MILLIS
+ val tooInTheFuture = currentTimeMillis + FIVE_MINUTES_IN_MILLIS
return age in tooInThePast..tooInTheFuture
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt
index a577daf9e4..1eac1d6b2d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt
@@ -52,7 +52,13 @@ data class RoomKeyWithHeldContent(
/**
* A human-readable reason for why the key was not sent. The receiving client should only use this string if it does not understand the code.
*/
- @Json(name = "reason") val reason: String? = null
+ @Json(name = "reason") val reason: String? = null,
+
+ /**
+ * the device ID of the device sending the m.room_key.withheld message
+ * MSC3735
+ */
+ @Json(name = "from_device") val fromDevice: String? = null
) {
val code: WithHeldCode?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index e3ccbad249..8e930f2a50 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -68,7 +68,8 @@ interface FileService {
mxcUrl = messageContent.getFileUrl(),
fileName = messageContent.getFileName(),
mimeType = messageContent.mimeType,
- elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
+ elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
+ )
/**
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
index 721a2bc8af..3bb8fad810 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
@@ -131,7 +131,7 @@ interface SharedSecretStorageService {
fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?): IntegrityResult
- fun requestSecret(name: String, myOtherDeviceId: String)
+ suspend fun requestSecret(name: String, myOtherDeviceId: String)
data class KeyRef(
val keyId: String?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index 8784d85c10..f1cfe3fee5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -379,24 +379,33 @@ internal class DefaultAuthenticationService @Inject constructor(
throw MatrixIdFailure.InvalidMatrixId
}
- return getWellknownTask.execute(GetWellknownTask.Params(
- domain = matrixId.getDomain(),
- homeServerConnectionConfig = homeServerConnectionConfig)
+ return getWellknownTask.execute(
+ GetWellknownTask.Params(
+ domain = matrixId.getDomain(),
+ homeServerConnectionConfig = homeServerConnectionConfig.orWellKnownDefaults()
+ )
)
}
+ private fun HomeServerConnectionConfig?.orWellKnownDefaults() = this ?: HomeServerConnectionConfig.Builder()
+ // server uri is ignored when doing a wellknown lookup as we use the matrix id domain instead
+ .withHomeServerUri("https://dummy.org")
+ .build()
+
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String,
deviceId: String?): Session {
- return directLoginTask.execute(DirectLoginTask.Params(
- homeServerConnectionConfig = homeServerConnectionConfig,
- userId = matrixId,
- password = password,
- deviceName = initialDeviceName,
- deviceId = deviceId
- ))
+ return directLoginTask.execute(
+ DirectLoginTask.Params(
+ homeServerConnectionConfig = homeServerConnectionConfig,
+ userId = matrixId,
+ password = password,
+ deviceName = initialDeviceName,
+ deviceId = deviceId
+ )
+ )
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt
index a92384b4ed..08d683af7f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt
@@ -21,9 +21,11 @@ import io.realm.annotations.RealmModule
/**
* Realm module for authentication classes
*/
-@RealmModule(library = true,
+@RealmModule(
+ library = true,
classes = [
SessionParamsEntity::class,
PendingSessionEntity::class
- ])
+ ]
+)
internal class AuthRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt
index 8e4043c11b..1296ea7cc4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt
@@ -44,7 +44,8 @@ internal class PendingSessionMapper @Inject constructor(moshi: Moshi) {
resetPasswordData = resetPasswordData,
currentSession = entity.currentSession,
isRegistrationStarted = entity.isRegistrationStarted,
- currentThreePidData = threePidData)
+ currentThreePidData = threePidData
+ )
}
fun map(sessionData: PendingSessionData?): PendingSessionEntity? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt
index 147c0e8be0..86929b1afe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt
@@ -54,6 +54,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
sessionParams.userId,
credentialsJson,
homeServerConnectionConfigJson,
- sessionParams.isTokenValid)
+ sessionParams.isTokenValid
+ )
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
index 590b333e90..890cb68aad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -120,21 +120,25 @@ internal class DefaultRegistrationWizard(
RegisterAddThreePidTask.Params(
threePid,
pendingSessionData.clientSecret,
- pendingSessionData.sendAttempt))
+ pendingSessionData.sendAttempt
+ )
+ )
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
.also { pendingSessionStore.savePendingSessionData(it) }
val params = RegistrationParams(
auth = if (threePid is RegisterThreePid.Email) {
- AuthParams.createForEmailIdentity(safeSession,
+ AuthParams.createForEmailIdentity(
+ safeSession,
ThreePidCredentials(
clientSecret = pendingSessionData.clientSecret,
sid = response.sid
)
)
} else {
- AuthParams.createForMsisdnIdentity(safeSession,
+ AuthParams.createForMsisdnIdentity(
+ safeSession,
ThreePidCredentials(
clientSecret = pendingSessionData.clientSecret,
sid = response.sid
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt
index d07d5ecd64..203dbcc60e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt
@@ -74,8 +74,8 @@ internal fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
* Indicate if the homeserver support MSC3440 for threads
*/
internal fun Versions.doesServerSupportThreads(): Boolean {
- return getMaxVersion() >= HomeServerVersion.v1_3_0 ||
- unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
+ // TODO: Check for v1.3 or whichever spec version formally specifies MSC3440.
+ return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
}
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
deleted file mode 100644
index 4380e31bff..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.crypto
-
-import android.content.Context
-import androidx.work.WorkerParameters
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.failure.shouldBeRetried
-import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-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.toContent
-import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
-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.createUniqueTxnId
-import org.matrix.android.sdk.internal.session.SessionComponent
-import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
-import org.matrix.android.sdk.internal.worker.SessionWorkerParams
-import javax.inject.Inject
-
-internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
- SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
-
- @JsonClass(generateAdapter = true)
- internal data class Params(
- override val sessionId: String,
- val requestId: String,
- val recipients: Map>,
- // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
- // to use the same value if this worker is retried.
- val txnId: String? = null,
- override val lastFailureMessage: String? = null
- ) : SessionWorkerParams {
- companion object {
- fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
- return Params(
- sessionId = sessionId,
- requestId = request.requestId,
- recipients = request.recipients,
- txnId = createUniqueTxnId(),
- lastFailureMessage = null
- )
- }
- }
- }
-
- @Inject lateinit var sendToDeviceTask: SendToDeviceTask
- @Inject lateinit var cryptoStore: IMXCryptoStore
- @Inject lateinit var credentials: Credentials
-
- override fun injectWith(injector: SessionComponent) {
- injector.inject(this)
- }
-
- override suspend fun doSafeWork(params: Params): Result {
- // params.txnId should be provided in all cases now. But Params can be deserialized by
- // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
- // So if not present, we create a txnId
- val txnId = params.txnId ?: createUniqueTxnId()
- val contentMap = MXUsersDevicesMap()
- val toDeviceContent = ShareRequestCancellation(
- requestingDeviceId = credentials.deviceId,
- requestId = params.requestId
- )
- cryptoStore.saveGossipingEvent(Event(
- type = EventType.ROOM_KEY_REQUEST,
- content = toDeviceContent.toContent(),
- senderId = credentials.userId
- ).also {
- it.ageLocalTs = System.currentTimeMillis()
- })
-
- params.recipients.forEach { userToDeviceMap ->
- userToDeviceMap.value.forEach { deviceId ->
- contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
- }
- }
-
- try {
- cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLING)
- sendToDeviceTask.execute(
- SendToDeviceTask.Params(
- eventType = EventType.ROOM_KEY_REQUEST,
- contentMap = contentMap,
- transactionId = txnId
- )
- )
- cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
- return Result.success()
- } catch (throwable: Throwable) {
- return if (throwable.shouldBeRetried()) {
- Result.retry()
- } else {
- cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
- buildErrorResult(params, throwable.localizedMessage ?: "error")
- }
- }
- }
-
- override fun buildErrorParams(params: Params, message: String): Params {
- return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 6a57d94677..fd4bf6adfd 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -42,12 +42,14 @@ import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
+import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
@@ -57,15 +59,13 @@ 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.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+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.events.model.Content
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.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
-import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
@@ -76,7 +76,6 @@ 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.algorithms.IMXEncrypting
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
-import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
@@ -91,6 +90,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
+import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.UserId
@@ -103,6 +103,7 @@ import org.matrix.android.sdk.internal.task.TaskThread
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
@@ -130,6 +131,7 @@ internal class DefaultCryptoService @Inject constructor(
private val userId: String,
@DeviceId
private val deviceId: String?,
+ private val clock: Clock,
private val myDeviceInfoHolder: Lazy,
// the crypto store
private val cryptoStore: IMXCryptoStore,
@@ -154,9 +156,10 @@ internal class DefaultCryptoService @Inject constructor(
private val crossSigningService: DefaultCrossSigningService,
//
- private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
+ private val incomingKeyRequestManager: IncomingKeyRequestManager,
+ private val secretShareManager: SecretShareManager,
//
- private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+ private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
// Actions
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val megolmSessionDataImporter: MegolmSessionDataImporter,
@@ -176,6 +179,7 @@ internal class DefaultCryptoService @Inject constructor(
private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope,
private val eventDecryptor: EventDecryptor,
+ private val verificationMessageProcessor: VerificationMessageProcessor,
private val liveEventManager: Lazy
) : CryptoService {
@@ -190,7 +194,7 @@ internal class DefaultCryptoService @Inject constructor(
}
}
- fun onLiveEvent(roomId: String, event: Event) {
+ fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
// handle state events
if (event.isStateEvent()) {
when (event.type) {
@@ -199,9 +203,18 @@ internal class DefaultCryptoService @Inject constructor(
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
}
}
+
+ // handle verification
+ if (!isInitialSync) {
+ if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
+ cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
+ verificationMessageProcessor.process(event)
+ }
+ }
+ }
}
- val gossipingBuffer = mutableListOf()
+// val gossipingBuffer = mutableListOf()
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback) {
setDeviceNameTask
@@ -377,27 +390,8 @@ internal class DefaultCryptoService @Inject constructor(
// Open the store
cryptoStore.open()
- runCatching {
-// if (isInitialSync) {
-// // refresh the devices list for each known room members
-// deviceListManager.invalidateAllDeviceLists()
-// deviceListManager.refreshOutdatedDeviceLists()
-// } else {
-
- // Why would we do that? it will be called at end of syn
- incomingGossipingRequestManager.processReceivedGossipingRequests()
-// }
- }.fold(
- {
- isStarting.set(false)
- isStarted.set(true)
- },
- {
- isStarting.set(false)
- isStarted.set(false)
- Timber.tag(loggerTag.value).e(it, "Start failed")
- }
- )
+ isStarting.set(false)
+ isStarted.set(true)
}
/**
@@ -405,7 +399,8 @@ internal class DefaultCryptoService @Inject constructor(
*/
fun close() = runBlocking(coroutineDispatchers.crypto) {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
- incomingGossipingRequestManager.close()
+ incomingKeyRequestManager.close()
+ outgoingKeyRequestManager.close()
olmDevice.release()
cryptoStore.close()
}
@@ -470,15 +465,28 @@ internal class DefaultCryptoService @Inject constructor(
}
oneTimeKeysUploader.maybeUploadOneTimeKeys()
- incomingGossipingRequestManager.processReceivedGossipingRequests()
}
- }
- tryOrNull {
- gossipingBuffer.toList().let {
- cryptoStore.saveGossipingEvents(it)
+ // Process pending key requests
+ try {
+ if (toDevices.isEmpty()) {
+ // this is not blocking
+ outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
+ } else {
+ Timber.tag(loggerTag.value)
+ .w("Don't process key requests yet as there might be more to_device to catchup")
+ }
+ } catch (failure: Throwable) {
+ // just for safety but should not throw
+ Timber.tag(loggerTag.value).w("failed to process pending request")
+ }
+
+ try {
+ incomingKeyRequestManager.processIncomingRequests()
+ } catch (failure: Throwable) {
+ // just for safety but should not throw
+ Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
}
- gossipingBuffer.clear()
}
}
}
@@ -592,7 +600,7 @@ internal class DefaultCryptoService @Inject constructor(
// (for now at least. Maybe we should alert the user somehow?)
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
- if (existingAlgorithm == algorithm && roomEncryptorsStore.get(roomId) != null) {
+ if (existingAlgorithm == algorithm) {
// ignore
Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Ignoring m.room.encryption for same alg ($algorithm) in $roomId")
return false
@@ -701,17 +709,19 @@ internal class DefaultCryptoService @Inject constructor(
}
val safeAlgorithm = alg
if (safeAlgorithm != null) {
- val t0 = System.currentTimeMillis()
+ val t0 = clock.epochMillis()
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
runCatching {
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
- Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
+ Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
} else {
val algorithm = getEncryptionAlgorithm(roomId)
- val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
- algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
+ val reason = String.format(
+ MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
+ algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
+ )
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
}
@@ -783,19 +793,25 @@ internal class DefaultCryptoService @Inject constructor(
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
- gossipingBuffer.add(event)
// Keys are imported directly, not waiting for end of sync
onRoomKeyEvent(event)
}
- EventType.REQUEST_SECRET,
+ EventType.REQUEST_SECRET -> {
+ secretShareManager.handleSecretRequest(event)
+ }
EventType.ROOM_KEY_REQUEST -> {
- // save audit trail
- gossipingBuffer.add(event)
- // Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
- incomingGossipingRequestManager.onGossipingRequestEvent(event)
+ event.getClearContent().toModel()?.let { req ->
+ // We'll always get these because we send room key requests to
+ // '*' (ie. 'all devices') which includes the sending device,
+ // so ignore requests from ourself because apart from it being
+ // very silly, it won't work because an Olm session cannot send
+ // messages to itself.
+ if (req.requestingDeviceId != deviceId) { // ignore self requests
+ event.senderId?.let { incomingKeyRequestManager.addNewIncomingRequest(it, req) }
+ }
+ }
}
EventType.SEND_SECRET -> {
- gossipingBuffer.add(event)
onSecretSendReceived(event)
}
EventType.ROOM_KEY_WITHHELD -> {
@@ -833,50 +849,38 @@ internal class DefaultCryptoService @Inject constructor(
val withHeldContent = event.getClearContent().toModel() ?: return Unit.also {
Timber.tag(loggerTag.value).i("Malformed onKeyWithHeldReceived() : missing fields")
}
- Timber.tag(loggerTag.value).i("onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
- val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm)
- if (alg is IMXWithHeldExtension) {
- alg.onRoomKeyWithHeldEvent(withHeldContent)
- } else {
- Timber.tag(loggerTag.value).e("onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
- return
+ val senderId = event.senderId ?: return Unit.also {
+ Timber.tag(loggerTag.value).i("Malformed onKeyWithHeldReceived() : missing fields")
}
+ withHeldContent.sessionId ?: return
+ withHeldContent.algorithm ?: return
+ withHeldContent.roomId ?: return
+ withHeldContent.senderKey ?: return
+ outgoingKeyRequestManager.onRoomKeyWithHeld(
+ sessionId = withHeldContent.sessionId,
+ algorithm = withHeldContent.algorithm,
+ roomId = withHeldContent.roomId,
+ senderKey = withHeldContent.senderKey,
+ fromDevice = withHeldContent.fromDevice,
+ event = Event(
+ type = EventType.ROOM_KEY_WITHHELD,
+ senderId = senderId,
+ content = event.getClearContent()
+ )
+ )
}
- private fun onSecretSendReceived(event: Event) {
- Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
- if (!event.isEncrypted()) {
- // secret send messages must be encrypted
- Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() :Received unencrypted secret send event")
- return
- }
-
- // Was that sent by us?
- if (event.senderId != userId) {
- Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
- return
- }
-
- val secretContent = event.getClearContent().toModel() ?: return
-
- val existingRequest = cryptoStore
- .getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
-
- if (existingRequest == null) {
- Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
- return
- }
-
- if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
- // TODO Ask to application layer?
- Timber.tag(loggerTag.value).v("onSecretSend() : secret not handled by SDK")
+ private suspend fun onSecretSendReceived(event: Event) {
+ secretShareManager.onSecretSendReceived(event) { secretName, secretValue ->
+ handleSDKLevelGossip(secretName, secretValue)
}
}
/**
* Returns true if handled by SDK, otherwise should be sent to application layer
*/
- private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
+ private fun handleSDKLevelGossip(secretName: String?,
+ secretValue: String): Boolean {
return when (secretName) {
MASTER_KEY_SSSS_NAME -> {
crossSigningService.onSecretMSKGossip(secretValue)
@@ -1022,9 +1026,9 @@ internal class DefaultCryptoService @Inject constructor(
return withContext(coroutineDispatchers.crypto) {
Timber.tag(loggerTag.value).v("importRoomKeys starts")
- val t0 = System.currentTimeMillis()
+ val t0 = clock.epochMillis()
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
- val t1 = System.currentTimeMillis()
+ val t1 = clock.epochMillis()
Timber.tag(loggerTag.value).v("importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
@@ -1032,7 +1036,7 @@ internal class DefaultCryptoService @Inject constructor(
.adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
.fromJson(roomKeys)
- val t2 = System.currentTimeMillis()
+ val t2 = clock.epochMillis()
Timber.tag(loggerTag.value).v("importRoomKeys : JSON parsing ${t2 - t1} ms")
@@ -1091,6 +1095,12 @@ internal class DefaultCryptoService @Inject constructor(
cryptoStore.setGlobalBlacklistUnverifiedDevices(block)
}
+ override fun enableKeyGossiping(enable: Boolean) {
+ cryptoStore.enableKeyGossiping(enable)
+ }
+
+ override fun isKeyGossipingEnabled() = cryptoStore.isKeyGossipingEnabled()
+
/**
* Tells whether the client should ever send encrypted messages to unverified devices.
* The default value is false.
@@ -1154,52 +1164,17 @@ internal class DefaultCryptoService @Inject constructor(
setRoomBlacklistUnverifiedDevices(roomId, false)
}
-// TODO Check if this method is still necessary
- /**
- * Cancel any earlier room key request
- *
- * @param requestBody requestBody
- */
- override fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
- outgoingGossipingRequestManager.cancelRoomKeyRequest(requestBody)
- }
-
/**
* Re request the encryption keys required to decrypt an event.
*
* @param event the event to decrypt again.
*/
override fun reRequestRoomKeyForEvent(event: Event) {
- val wireContent = event.content.toModel() ?: return Unit.also {
- Timber.tag(loggerTag.value).e("reRequestRoomKeyForEvent Failed to re-request key, null content")
- }
-
- val requestBody = RoomKeyRequestBody(
- algorithm = wireContent.algorithm,
- roomId = event.roomId,
- senderKey = wireContent.senderKey,
- sessionId = wireContent.sessionId
- )
-
- outgoingGossipingRequestManager.resendRoomKeyRequest(requestBody)
+ outgoingKeyRequestManager.requestKeyForEvent(event, true)
}
override fun requestRoomKeyForEvent(event: Event) {
- val wireContent = event.content.toModel() ?: return Unit.also {
- Timber.tag(loggerTag.value).e("requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
- }
-
- cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-// if (!isStarted()) {
-// Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init")
-// internalStart(false)
-// }
- roomDecryptorProvider
- .getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
- ?.requestKeysForEvent(event, false) ?: run {
- Timber.tag(loggerTag.value).v("requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
- }
- }
+ outgoingKeyRequestManager.requestKeyForEvent(event, false)
}
/**
@@ -1208,7 +1183,8 @@ internal class DefaultCryptoService @Inject constructor(
* @param listener listener
*/
override fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
- incomingGossipingRequestManager.addRoomKeysRequestListener(listener)
+ incomingKeyRequestManager.addRoomKeysRequestListener(listener)
+ secretShareManager.addListener(listener)
}
/**
@@ -1217,42 +1193,10 @@ internal class DefaultCryptoService @Inject constructor(
* @param listener listener
*/
override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
- incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
+ incomingKeyRequestManager.removeRoomKeysRequestListener(listener)
+ secretShareManager.removeListener(listener)
}
-// private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
-// val deviceKey = deviceInfo.identityKey()
-//
-// val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
-// val now = System.currentTimeMillis()
-// if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
-// Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
-// return
-// }
-//
-// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
-// lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
-//
-// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-// ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
-//
-// // Now send a blank message on that session so the other side knows about it.
-// // (The keyshare request is sent in the clear so that won't do)
-// // We send this first such that, as long as the toDevice messages arrive in the
-// // same order we sent them, the other end will get this first, set up the new session,
-// // then get the keyshare request and send the key over this new session (because it
-// // is the session it has most recently received a message on).
-// val payloadJson = mapOf("type" to EventType.DUMMY)
-//
-// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
-// val sendToDeviceMap = MXUsersDevicesMap()
-// sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
-// Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
-// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
-// sendToDeviceTask.execute(sendToDeviceParams)
-// }
-// }
-
/**
* Provides the list of unknown devices
*
@@ -1298,27 +1242,41 @@ internal class DefaultCryptoService @Inject constructor(
return "DefaultCryptoService of $userId ($deviceId)"
}
- override fun getOutgoingRoomKeyRequests(): List {
+ override fun getOutgoingRoomKeyRequests(): List {
return cryptoStore.getOutgoingRoomKeyRequests()
}
- override fun getOutgoingRoomKeyRequestsPaged(): LiveData> {
+ override fun getOutgoingRoomKeyRequestsPaged(): LiveData> {
return cryptoStore.getOutgoingRoomKeyRequestsPaged()
}
- override fun getIncomingRoomKeyRequestsPaged(): LiveData> {
- return cryptoStore.getIncomingRoomKeyRequestsPaged()
- }
-
override fun getIncomingRoomKeyRequests(): List {
- return cryptoStore.getIncomingRoomKeyRequests()
+ return cryptoStore.getGossipingEvents()
+ .mapNotNull {
+ IncomingRoomKeyRequest.fromEvent(it)
+ }
}
- override fun getGossipingEventsTrail(): LiveData> {
+ override fun getIncomingRoomKeyRequestsPaged(): LiveData> {
+ return cryptoStore.getGossipingEventsTrail(TrailType.IncomingKeyRequest) {
+ IncomingRoomKeyRequest.fromEvent(it)
+ ?: IncomingRoomKeyRequest(localCreationTimestamp = 0L)
+ }
+ }
+
+ /**
+ * If you registered a `GossipingRequestListener`, you will be notified of key request
+ * that was not accepted by the SDK. You can call back this manually to accept anyhow.
+ */
+ override suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) {
+ incomingKeyRequestManager.manuallyAcceptRoomKeyRequest(request)
+ }
+
+ override fun getGossipingEventsTrail(): LiveData> {
return cryptoStore.getGossipingEventsTrail()
}
- override fun getGossipingEvents(): List {
+ override fun getGossipingEvents(): List {
return cryptoStore.getGossipingEvents()
}
@@ -1342,8 +1300,8 @@ internal class DefaultCryptoService @Inject constructor(
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
- callback.onFailure(failure)
- return@launch
+ // we probably shouldn't block sending on that (but questionable)
+ // but some members won't be able to decrypt
}
val userIds = getRoomUserIds(roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 6cae2d0935..f546b35fcf 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -31,19 +31,23 @@ import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.logLimit
+import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
import javax.inject.Inject
// Legacy name: MXDeviceList
@SessionScope
-internal class DeviceListManager @Inject constructor(private val cryptoStore: IMXCryptoStore,
- private val olmDevice: MXOlmDevice,
- private val syncTokenStore: SyncTokenStore,
- private val credentials: Credentials,
- private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
- private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
- coroutineDispatchers: MatrixCoroutineDispatchers,
- private val taskExecutor: TaskExecutor) {
+internal class DeviceListManager @Inject constructor(
+ private val cryptoStore: IMXCryptoStore,
+ private val olmDevice: MXOlmDevice,
+ private val syncTokenStore: SyncTokenStore,
+ private val credentials: Credentials,
+ private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
+ private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+ coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val taskExecutor: TaskExecutor,
+ private val clock: Clock,
+) {
interface UserDevicesUpdateListener {
fun onUsersDeviceUpdate(userIds: List)
@@ -310,11 +314,20 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
stored
} else {
Timber.v("## CRYPTO | downloadKeys() : starts")
- val t0 = System.currentTimeMillis()
- val result = doKeyDownloadForUsers(downloadUsers)
- Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
- result.also {
- it.addEntriesFromMap(stored)
+ val t0 = clock.epochMillis()
+ try {
+ val result = doKeyDownloadForUsers(downloadUsers)
+ Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${clock.epochMillis() - t0} ms")
+ result.also {
+ it.addEntriesFromMap(stored)
+ }
+ } catch (failure: Throwable) {
+ Timber.w(failure, "## CRYPTO | downloadKeys() : doKeyDownloadForUsers failed after ${clock.epochMillis() - t0} ms")
+ if (forceDownload) {
+ throw failure
+ } else {
+ stored
+ }
}
}
}
@@ -475,8 +488,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
if (!isVerified) {
- Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
- deviceKeys.deviceId + " with error " + errorMessage)
+ Timber.e(
+ "## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
+ deviceKeys.deviceId + " with error " + errorMessage
+ )
return false
}
@@ -486,9 +501,11 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
// best off sticking with the original keys.
//
// Should we warn the user about it somehow?
- Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
- deviceKeys.deviceId + " has changed : " +
- previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
+ Timber.e(
+ "## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
+ deviceKeys.deviceId + " has changed : " +
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey
+ )
Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index 1c8bce7377..a094189645 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
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.util.time.Clock
import timber.log.Timber
import javax.inject.Inject
@@ -47,6 +48,7 @@ private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
internal class EventDecryptor @Inject constructor(
private val cryptoCoroutineScope: CoroutineScope,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val clock: Clock,
private val roomDecryptorProvider: RoomDecryptorProvider,
private val messageEncrypter: MessageEncrypter,
private val sendToDeviceTask: SendToDeviceTask,
@@ -153,7 +155,7 @@ internal class EventDecryptor @Inject constructor(
// we should force start a new session for those
Timber.tag(loggerTag.value).v("Unwedging: ${wedgedDevices.size} are wedged")
// get the one that should be retried according to rate limit
- val now = System.currentTimeMillis()
+ val now = clock.epochMillis()
val toUnwedge = wedgedDevices.filter {
val lastForcedDate = lastNewSessionForcedDates[it] ?: 0
if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
deleted file mode 100644
index 0013c31eea..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.crypto
-
-import androidx.work.BackoffPolicy
-import androidx.work.Data
-import androidx.work.ExistingWorkPolicy
-import androidx.work.ListenableWorker
-import androidx.work.OneTimeWorkRequest
-import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.internal.di.WorkManagerProvider
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.util.CancelableWork
-import org.matrix.android.sdk.internal.worker.startChain
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-@SessionScope
-internal class GossipingWorkManager @Inject constructor(
- private val workManagerProvider: WorkManagerProvider
-) {
-
- inline fun createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
- return workManagerProvider.matrixOneTimeWorkRequestBuilder()
- .setConstraints(WorkManagerProvider.workConstraints)
- .startChain(startChain)
- .setInputData(data)
- .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
- .build()
- }
-
- // Prevent sending queue to stay broken after app restart
- // The unique queue id will stay the same as long as this object is instanciated
- val queueSuffixApp = System.currentTimeMillis()
-
- fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
- workManagerProvider.workManager
- .beginUniqueWork(this::class.java.name + "_$queueSuffixApp", policy, workRequest)
- .enqueue()
-
- return CancelableWork(workManagerProvider.workManager, workRequest.id)
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
deleted file mode 100644
index b907b57f82..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.crypto
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-import org.matrix.android.sdk.api.crypto.MXCryptoConfig
-import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
-import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
-import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.util.toBase64NoPadding
-import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
-import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.di.SessionId
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
-import timber.log.Timber
-import java.util.concurrent.Executors
-import javax.inject.Inject
-
-@SessionScope
-internal class IncomingGossipingRequestManager @Inject constructor(
- @SessionId private val sessionId: String,
- private val credentials: Credentials,
- private val cryptoStore: IMXCryptoStore,
- private val cryptoConfig: MXCryptoConfig,
- private val gossipingWorkManager: GossipingWorkManager,
- private val roomEncryptorsStore: RoomEncryptorsStore,
- private val roomDecryptorProvider: RoomDecryptorProvider,
- private val coroutineDispatchers: MatrixCoroutineDispatchers,
- private val cryptoCoroutineScope: CoroutineScope) {
-
- private val executor = Executors.newSingleThreadExecutor()
-
- // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
- // we received in the current sync.
- private val receivedGossipingRequests = ArrayList()
- private val receivedRequestCancellations = ArrayList()
-
- // the listeners
- private val gossipingRequestListeners: MutableSet = HashSet()
-
- init {
- receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests())
- }
-
- fun close() {
- executor.shutdownNow()
- }
-
- // Recently verified devices (map of deviceId and timestamp)
- private val recentlyVerifiedDevices = HashMap()
-
- /**
- * Called when a session has been verified.
- * This information can be used by the manager to decide whether or not to fullfil gossiping requests
- */
- fun onVerificationCompleteForDevice(deviceId: String) {
- // For now we just keep an in memory cache
- synchronized(recentlyVerifiedDevices) {
- recentlyVerifiedDevices[deviceId] = System.currentTimeMillis()
- }
- }
-
- private fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean {
- val verifTimestamp: Long?
- synchronized(recentlyVerifiedDevices) {
- verifTimestamp = recentlyVerifiedDevices[deviceId]
- }
- if (verifTimestamp == null) return false
-
- val age = System.currentTimeMillis() - verifTimestamp
-
- return age < FIVE_MINUTES_IN_MILLIS
- }
-
- /**
- * Called when we get an m.room_key_request event
- * It must be called on CryptoThread
- *
- * @param event the announcement event.
- */
- fun onGossipingRequestEvent(event: Event) {
- val roomKeyShare = event.getClearContent().toModel()
- Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare")
- // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
- when (roomKeyShare?.action) {
- GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
- if (event.getClearType() == EventType.REQUEST_SECRET) {
- IncomingSecretShareRequest.fromEvent(event)?.let {
- if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
- // ignore, it was sent by me as *
- Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
- } else {
-// // save in DB
-// cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
- receivedGossipingRequests.add(it)
- }
- }
- } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
- IncomingRoomKeyRequest.fromEvent(event)?.let {
- if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
- // ignore, it was sent by me as *
- Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
- } else {
-// cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
- receivedGossipingRequests.add(it)
- }
- }
- }
- }
- GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> {
- IncomingRequestCancellation.fromEvent(event)?.let {
- receivedRequestCancellations.add(it)
- }
- }
- else -> {
- Timber.e("## GOSSIP onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}")
- }
- }
- }
-
- /**
- * Process any m.room_key_request or m.secret.request events which were queued up during the
- * current sync.
- * It must be called on CryptoThread
- */
- fun processReceivedGossipingRequests() {
- val roomKeyRequestsToProcess = receivedGossipingRequests.toList()
- receivedGossipingRequests.clear()
-
- Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : ${roomKeyRequestsToProcess.size} request to process")
-
- var receivedRequestCancellations: List? = null
-
- synchronized(this.receivedRequestCancellations) {
- if (this.receivedRequestCancellations.isNotEmpty()) {
- receivedRequestCancellations = this.receivedRequestCancellations.toList()
- this.receivedRequestCancellations.clear()
- }
- }
-
- executor.execute {
- cryptoStore.storeIncomingGossipingRequests(roomKeyRequestsToProcess)
- for (request in roomKeyRequestsToProcess) {
- if (request is IncomingRoomKeyRequest) {
- processIncomingRoomKeyRequest(request)
- } else if (request is IncomingSecretShareRequest) {
- processIncomingSecretShareRequest(request)
- }
- }
-
- receivedRequestCancellations?.forEach { request ->
- Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
- // we should probably only notify the app of cancellations we told it
- // about, but we don't currently have a record of that, so we just pass
- // everything through.
- if (request.userId == credentials.userId && request.deviceId == credentials.deviceId) {
- // ignore remote echo
- return@forEach
- }
- val matchingIncoming = cryptoStore.getIncomingRoomKeyRequest(request.userId ?: "", request.deviceId ?: "", request.requestId ?: "")
- if (matchingIncoming == null) {
- // ignore that?
- return@forEach
- } else {
- // If it was accepted from this device, keep the information, do not mark as cancelled
- if (matchingIncoming.state != GossipingRequestState.ACCEPTED) {
- onRoomKeyRequestCancellation(request)
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.CANCELLED_BY_REQUESTER)
- }
- }
- }
- }
- }
-
- private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) {
- val userId = request.userId ?: return
- val deviceId = request.deviceId ?: return
- val body = request.requestBody ?: return
- val roomId = body.roomId ?: return
- val alg = body.algorithm ?: return
-
- Timber.v("## CRYPTO | GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
- if (credentials.userId != userId) {
- handleKeyRequestFromOtherUser(body, request, alg, roomId, userId, deviceId)
- return
- }
- // TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
- // if we don't have a decryptor for this room/alg, we don't have
- // the keys for the requested events, and can drop the requests.
- val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
- if (null == decryptor) {
- Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
- if (!decryptor.hasKeysForKeyRequest(request)) {
- Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
-
- if (credentials.deviceId == deviceId && credentials.userId == userId) {
- Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : oneself device - ignored")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
- request.share = Runnable {
- decryptor.shareKeysWithDevice(request)
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
- }
- request.ignore = Runnable {
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- }
- // if the device is verified already, share the keys
- val device = cryptoStore.getUserDevice(userId, deviceId)
- if (device != null) {
- if (device.isVerified) {
- Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys")
- request.share?.run()
- return
- }
-
- if (device.isBlocked) {
- Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
- }
-
- // As per config we automatically discard untrusted devices request
- if (cryptoConfig.discardRoomKeyRequestsFromUntrustedDevices) {
- Timber.v("## CRYPTO | processReceivedGossipingRequests() : discardRoomKeyRequestsFromUntrustedDevices")
- // At this point the device is unknown, we don't want to bother user with that
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
-
- // Pass to application layer to decide what to do
- onRoomKeyRequest(request)
- }
-
- private fun handleKeyRequestFromOtherUser(body: RoomKeyRequestBody,
- request: IncomingRoomKeyRequest,
- alg: String,
- roomId: String,
- userId: String,
- deviceId: String) {
- Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
- val senderKey = body.senderKey ?: return Unit
- .also { Timber.w("missing senderKey") }
- .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
- val sessionId = body.sessionId ?: return Unit
- .also { Timber.w("missing sessionId") }
- .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-
- if (alg != MXCRYPTO_ALGORITHM_MEGOLM) {
- return Unit
- .also { Timber.w("Only megolm is accepted here") }
- .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
- }
-
- val roomEncryptor = roomEncryptorsStore.get(roomId) ?: return Unit
- .also { Timber.w("no room Encryptor") }
- .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-
- cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- if (roomEncryptor is IMXGroupEncryption) {
- val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
-
- if (isSuccess) {
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
- } else {
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
- }
- } else {
- Timber.e("## CRYPTO | handleKeyRequestFromOtherUser() from:$userId: Unable to handle IMXGroupEncryption.reshareKey for $alg")
- }
- }
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)
- }
-
- private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
- val secretName = request.secretName ?: return Unit.also {
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Missing secret name")
- }
-
- val userId = request.userId
- if (userId == null || credentials.userId != userId) {
- Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
-
- val deviceId = request.deviceId
- ?: return Unit.also {
- Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Malformed request, no ")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- }
-
- val device = cryptoStore.getUserDevice(userId, deviceId)
- ?: return Unit.also {
- Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- }
-
- if (!device.isVerified || device.isBlocked) {
- Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- return
- }
-
- val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified()
-
- when (secretName) {
- MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
- SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
- USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
- KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
- ?.let {
- extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
- }
- else -> null
- }?.let { secretValue ->
- Timber.i("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
- if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) {
- val params = SendGossipWorker.Params(
- sessionId = sessionId,
- secretValue = secretValue,
- requestUserId = request.userId,
- requestDeviceId = request.deviceId,
- requestId = request.requestId,
- txnId = createUniqueTxnId()
- )
-
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
- val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
- gossipingWorkManager.postWork(workRequest)
- } else {
- Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old")
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- }
- return
- }
-
- Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : $secretName unknown at SDK level, asking to app layer")
-
- request.ignore = Runnable {
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
- }
-
- request.share = { secretValue ->
- val params = SendGossipWorker.Params(
- sessionId = userId,
- secretValue = secretValue,
- requestUserId = request.userId,
- requestDeviceId = request.deviceId,
- requestId = request.requestId,
- txnId = createUniqueTxnId()
- )
-
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
- val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
- gossipingWorkManager.postWork(workRequest)
- cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
- }
-
- onShareRequest(request)
- }
-
- /**
- * Dispatch onRoomKeyRequest
- *
- * @param request the request
- */
- private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
- synchronized(gossipingRequestListeners) {
- for (listener in gossipingRequestListeners) {
- try {
- listener.onRoomKeyRequest(request)
- } catch (e: Exception) {
- Timber.e(e, "## CRYPTO | onRoomKeyRequest() failed")
- }
- }
- }
- }
-
- /**
- * Ask for a value to the listeners, and take the first one
- */
- private fun onShareRequest(request: IncomingSecretShareRequest) {
- synchronized(gossipingRequestListeners) {
- for (listener in gossipingRequestListeners) {
- try {
- if (listener.onSecretShareRequest(request)) {
- return
- }
- } catch (e: Exception) {
- Timber.e(e, "## CRYPTO | GOSSIP onRoomKeyRequest() failed")
- }
- }
- }
- // Not handled, ignore
- request.ignore?.run()
- }
-
- /**
- * A room key request cancellation has been received.
- *
- * @param request the cancellation request
- */
- private fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) {
- synchronized(gossipingRequestListeners) {
- for (listener in gossipingRequestListeners) {
- try {
- listener.onRoomKeyRequestCancellation(request)
- } catch (e: Exception) {
- Timber.e(e, "## CRYPTO | GOSSIP onRoomKeyRequestCancellation() failed")
- }
- }
- }
- }
-
- fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
- synchronized(gossipingRequestListeners) {
- gossipingRequestListeners.add(listener)
- }
- }
-
- fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
- synchronized(gossipingRequestListeners) {
- gossipingRequestListeners.remove(listener)
- }
- }
-
- companion object {
- private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
new file mode 100644
index 0000000000..13f2fb861a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
@@ -0,0 +1,463 @@
+/*
+ * 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.crypto
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
+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.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
+import org.matrix.android.sdk.internal.util.time.Clock
+import timber.log.Timber
+import java.util.concurrent.Executors
+import javax.inject.Inject
+import kotlin.system.measureTimeMillis
+
+private val loggerTag = LoggerTag("IncomingKeyRequestManager", LoggerTag.CRYPTO)
+
+@SessionScope
+internal class IncomingKeyRequestManager @Inject constructor(
+ private val credentials: Credentials,
+ private val cryptoStore: IMXCryptoStore,
+ private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
+ private val olmDevice: MXOlmDevice,
+ private val cryptoConfig: MXCryptoConfig,
+ private val messageEncrypter: MessageEncrypter,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val sendToDeviceTask: SendToDeviceTask,
+ private val clock: Clock,
+) {
+
+ private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+ private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher)
+ val sequencer = SemaphoreCoroutineSequencer()
+
+ private val incomingRequestBuffer = mutableListOf()
+
+ // the listeners
+ private val gossipingRequestListeners: MutableSet = HashSet()
+
+ enum class MegolmRequestAction {
+ Request, Cancel
+ }
+
+ data class ValidMegolmRequestBody(
+ val requestId: String,
+ val requestingUserId: String,
+ val requestingDeviceId: String,
+ val roomId: String,
+ val senderKey: String,
+ val sessionId: String,
+ val action: MegolmRequestAction
+ ) {
+ fun shortDbgString() = "Request from $requestingUserId|$requestingDeviceId for session $sessionId in room $roomId"
+ }
+
+ private fun RoomKeyShareRequest.toValidMegolmRequest(senderId: String): ValidMegolmRequestBody? {
+ val deviceId = requestingDeviceId ?: return null
+ val body = body ?: return null
+ val roomId = body.roomId ?: return null
+ val sessionId = body.sessionId ?: return null
+ val senderKey = body.senderKey ?: return null
+ val requestId = this.requestId ?: return null
+ if (body.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
+ val action = when (this.action) {
+ "request" -> MegolmRequestAction.Request
+ "request_cancellation" -> MegolmRequestAction.Cancel
+ else -> null
+ } ?: return null
+ return ValidMegolmRequestBody(
+ requestId = requestId,
+ requestingUserId = senderId,
+ requestingDeviceId = deviceId,
+ roomId = roomId,
+ senderKey = senderKey,
+ sessionId = sessionId,
+ action = action
+ )
+ }
+
+ fun addNewIncomingRequest(senderId: String, request: RoomKeyShareRequest) {
+ if (!cryptoStore.isKeyGossipingEnabled()) {
+ Timber.tag(loggerTag.value)
+ .i("Ignore incoming key request as per crypto config in room ${request.body?.roomId}")
+ return
+ }
+ outgoingRequestScope.launch {
+ // It is important to handle requests in order
+ sequencer.post {
+ val validMegolmRequest = request.toValidMegolmRequest(senderId) ?: return@post Unit.also {
+ Timber.tag(loggerTag.value).w("Received key request for unknown algorithm ${request.body?.algorithm}")
+ }
+
+ // is there already one like that?
+ val existing = incomingRequestBuffer.firstOrNull { it == validMegolmRequest }
+ if (existing == null) {
+ when (validMegolmRequest.action) {
+ MegolmRequestAction.Request -> {
+ // just add to the buffer
+ incomingRequestBuffer.add(validMegolmRequest)
+ }
+ MegolmRequestAction.Cancel -> {
+ // ignore, we can't cancel as it's not known (probably already processed)
+ // still notify app layer if it was passed up previously
+ IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq ->
+ outgoingRequestScope.launch(coroutineDispatchers.computation) {
+ val listenersCopy = synchronized(gossipingRequestListeners) {
+ gossipingRequestListeners.toList()
+ }
+ listenersCopy.onEach {
+ tryOrNull {
+ withContext(coroutineDispatchers.main) {
+ it.onRequestCancelled(iReq)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ when (validMegolmRequest.action) {
+ MegolmRequestAction.Request -> {
+ // it's already in buffer, nop keep existing
+ }
+ MegolmRequestAction.Cancel -> {
+ // discard the request in buffer
+ incomingRequestBuffer.remove(existing)
+ outgoingRequestScope.launch(coroutineDispatchers.computation) {
+ val listenersCopy = synchronized(gossipingRequestListeners) {
+ gossipingRequestListeners.toList()
+ }
+ listenersCopy.onEach {
+ IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq ->
+ withContext(coroutineDispatchers.main) {
+ tryOrNull { it.onRequestCancelled(iReq) }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun processIncomingRequests() {
+ outgoingRequestScope.launch {
+ sequencer.post {
+ measureTimeMillis {
+ Timber.tag(loggerTag.value).v("processIncomingKeyRequests : ${incomingRequestBuffer.size} request to process")
+ incomingRequestBuffer.forEach {
+ // should not happen, we only store requests
+ if (it.action != MegolmRequestAction.Request) return@forEach
+ try {
+ handleIncomingRequest(it)
+ } catch (failure: Throwable) {
+ // ignore and continue, should not happen
+ Timber.tag(loggerTag.value).w(failure, "processIncomingKeyRequests : failed to process request $it")
+ }
+ }
+ incomingRequestBuffer.clear()
+ }.let { duration ->
+ Timber.tag(loggerTag.value).v("Finish processing incoming key request in $duration ms")
+ }
+ }
+ }
+ }
+
+ private suspend fun handleIncomingRequest(request: ValidMegolmRequestBody) {
+ // We don't want to download keys, if we don't know the device yet we won't share any how?
+ val requestingDevice =
+ cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId)
+ ?: return Unit.also {
+ Timber.tag(loggerTag.value).d("Ignoring key request: ${request.shortDbgString()}")
+ }
+
+ cryptoStore.saveIncomingKeyRequestAuditTrail(
+ request.requestId,
+ request.roomId,
+ request.sessionId,
+ request.senderKey,
+ MXCRYPTO_ALGORITHM_MEGOLM,
+ request.requestingUserId,
+ request.requestingDeviceId
+ )
+
+ val roomAlgorithm = // withContext(coroutineDispatchers.crypto) {
+ cryptoStore.getRoomAlgorithm(request.roomId)
+// }
+ if (roomAlgorithm != MXCRYPTO_ALGORITHM_MEGOLM) {
+ // strange we received a request for a room that is not encrypted
+ // maybe a broken state?
+ Timber.tag(loggerTag.value).w("Received a key request in a room with unsupported alg:$roomAlgorithm , req:${request.shortDbgString()}")
+ return
+ }
+
+ // Is it for one of our sessions?
+ if (request.requestingUserId == credentials.userId) {
+ Timber.tag(loggerTag.value).v("handling request from own user: megolm session ${request.sessionId}")
+
+ if (request.requestingDeviceId == credentials.deviceId) {
+ // ignore it's a remote echo
+ return
+ }
+ // If it's verified we share from the early index we know
+ // if not we check if it was originaly shared or not
+ if (requestingDevice.isVerified) {
+ // we share from the earliest known chain index
+ shareMegolmKey(request, requestingDevice, null)
+ } else {
+ shareIfItWasPreviouslyShared(request, requestingDevice)
+ }
+ } else {
+ if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
+ Timber.tag(loggerTag.value).v("Ignore request from other user as per crypto config: ${request.shortDbgString()}")
+ return
+ }
+ Timber.tag(loggerTag.value).v("handling request from other user: megolm session ${request.sessionId}")
+ if (requestingDevice.isBlocked) {
+ // it's blocked, so send a withheld code
+ sendWithheldForRequest(request, WithHeldCode.BLACKLISTED)
+ } else {
+ shareIfItWasPreviouslyShared(request, requestingDevice)
+ }
+ }
+ }
+
+ private suspend fun shareIfItWasPreviouslyShared(request: ValidMegolmRequestBody, requestingDevice: CryptoDeviceInfo) {
+ // we don't reshare unless it was previously shared with
+ val wasSessionSharedWithUser = withContext(coroutineDispatchers.crypto) {
+ cryptoStore.getSharedSessionInfo(request.roomId, request.sessionId, requestingDevice)
+ }
+ if (wasSessionSharedWithUser.found && wasSessionSharedWithUser.chainIndex != null) {
+ // we share from the index it was previously shared with
+ shareMegolmKey(request, requestingDevice, wasSessionSharedWithUser.chainIndex.toLong())
+ } else {
+ val isOwnDevice = requestingDevice.userId == credentials.userId
+ sendWithheldForRequest(request, if (isOwnDevice) WithHeldCode.UNVERIFIED else WithHeldCode.UNAUTHORISED)
+ // if it's our device we could delegate to the app layer to decide
+ if (isOwnDevice) {
+ outgoingRequestScope.launch(coroutineDispatchers.computation) {
+ val listenersCopy = synchronized(gossipingRequestListeners) {
+ gossipingRequestListeners.toList()
+ }
+ val iReq = IncomingRoomKeyRequest(
+ userId = requestingDevice.userId,
+ deviceId = requestingDevice.deviceId,
+ requestId = request.requestId,
+ requestBody = RoomKeyRequestBody(
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ senderKey = request.senderKey,
+ sessionId = request.sessionId,
+ roomId = request.roomId
+ ),
+ localCreationTimestamp = clock.epochMillis()
+ )
+ listenersCopy.onEach {
+ withContext(coroutineDispatchers.main) {
+ tryOrNull { it.onRoomKeyRequest(iReq) }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun sendWithheldForRequest(request: ValidMegolmRequestBody, code: WithHeldCode) {
+ Timber.tag(loggerTag.value)
+ .w("Send withheld $code for req: ${request.shortDbgString()}")
+ val withHeldContent = RoomKeyWithHeldContent(
+ roomId = request.roomId,
+ senderKey = request.senderKey,
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ sessionId = request.sessionId,
+ codeString = code.value,
+ fromDevice = credentials.deviceId
+ )
+
+ val params = SendToDeviceTask.Params(
+ EventType.ROOM_KEY_WITHHELD,
+ MXUsersDevicesMap().apply {
+ setObject(request.requestingUserId, request.requestingDeviceId, withHeldContent)
+ }
+ )
+ try {
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.execute(params)
+ Timber.tag(loggerTag.value)
+ .d("Send withheld $code req: ${request.shortDbgString()}")
+ }
+
+ cryptoStore.saveWithheldAuditTrail(
+ roomId = request.roomId,
+ sessionId = request.sessionId,
+ senderKey = request.senderKey,
+ algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+ code = code,
+ userId = request.requestingUserId,
+ deviceId = request.requestingDeviceId
+ )
+ } catch (failure: Throwable) {
+ // Ignore it's not that important?
+ // do we want to fallback to a worker?
+ Timber.tag(loggerTag.value)
+ .w("Failed to send withheld $code req: ${request.shortDbgString()} reason:${failure.localizedMessage}")
+ }
+ }
+
+ suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) {
+ request.requestId ?: return
+ request.deviceId ?: return
+ request.userId ?: return
+ request.requestBody?.roomId ?: return
+ request.requestBody.senderKey ?: return
+ request.requestBody.sessionId ?: return
+ val validReq = ValidMegolmRequestBody(
+ requestId = request.requestId,
+ requestingDeviceId = request.deviceId,
+ requestingUserId = request.userId,
+ roomId = request.requestBody.roomId,
+ senderKey = request.requestBody.senderKey,
+ sessionId = request.requestBody.sessionId,
+ action = MegolmRequestAction.Request
+ )
+ val requestingDevice =
+ cryptoStore.getUserDevice(request.userId, request.deviceId)
+ ?: return Unit.also {
+ Timber.tag(loggerTag.value).d("Ignoring key request: ${validReq.shortDbgString()}")
+ }
+
+ shareMegolmKey(validReq, requestingDevice, null)
+ }
+
+ private suspend fun shareMegolmKey(validRequest: ValidMegolmRequestBody,
+ requestingDevice: CryptoDeviceInfo,
+ chainIndex: Long?): Boolean {
+ Timber.tag(loggerTag.value)
+ .d("try to re-share Megolm Key at index $chainIndex for ${validRequest.shortDbgString()}")
+
+ val devicesByUser = mapOf(validRequest.requestingUserId to listOf(requestingDevice))
+ val usersDeviceMap = try {
+ ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value)
+ .w("Failed to establish olm session")
+ sendWithheldForRequest(validRequest, WithHeldCode.NO_OLM)
+ return false
+ }
+
+ val olmSessionResult = usersDeviceMap.getObject(requestingDevice.userId, requestingDevice.deviceId)
+ if (olmSessionResult?.sessionId == null) {
+ Timber.tag(loggerTag.value)
+ .w("reshareKey: no session with this device, probably because there were no one-time keys")
+ sendWithheldForRequest(validRequest, WithHeldCode.NO_OLM)
+ return false
+ }
+ val sessionHolder = try {
+ olmDevice.getInboundGroupSession(validRequest.sessionId, validRequest.senderKey, validRequest.roomId)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value)
+ .e(failure, "shareKeysWithDevice: failed to get session ${validRequest.requestingUserId}")
+ // It's unavailable
+ sendWithheldForRequest(validRequest, WithHeldCode.UNAVAILABLE)
+ return false
+ }
+
+ val export = sessionHolder.mutex.withLock {
+ sessionHolder.wrapper.exportKeys(chainIndex)
+ } ?: return false.also {
+ Timber.tag(loggerTag.value)
+ .e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}")
+ }
+
+ val payloadJson = mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to export
+ )
+
+ val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(requestingDevice))
+ val sendToDeviceMap = MXUsersDevicesMap()
+ sendToDeviceMap.setObject(requestingDevice.userId, requestingDevice.deviceId, encodedPayload)
+ Timber.tag(loggerTag.value).d("reshareKey() : try sending session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
+ val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+ return try {
+ sendToDeviceTask.execute(sendToDeviceParams)
+ Timber.tag(loggerTag.value)
+ .i("successfully re-shared session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
+ cryptoStore.saveForwardKeyAuditTrail(
+ validRequest.roomId,
+ validRequest.sessionId,
+ validRequest.senderKey,
+ MXCRYPTO_ALGORITHM_MEGOLM,
+ requestingDevice.userId,
+ requestingDevice.deviceId,
+ chainIndex
+ )
+ true
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value)
+ .e(failure, "fail to re-share session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
+ false
+ }
+ }
+
+ fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
+ synchronized(gossipingRequestListeners) {
+ gossipingRequestListeners.add(listener)
+ }
+ }
+
+ fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
+ synchronized(gossipingRequestListeners) {
+ gossipingRequestListeners.remove(listener)
+ }
+ }
+
+ fun close() {
+ try {
+ outgoingRequestScope.cancel("User Terminate")
+ incomingRequestBuffer.clear()
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).w("Failed to shutDown request manager")
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
index f8235bf344..89e38cb7cf 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
@@ -29,6 +29,7 @@ import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.and
import kotlin.experimental.xor
import kotlin.math.min
+import kotlin.system.measureTimeMillis
/**
* Utility class to import/export the crypto data
@@ -310,40 +311,40 @@ internal object MXMegolmExportEncryption {
*/
@Throws(Exception::class)
private fun deriveKeys(salt: ByteArray, iterations: Int, password: String): ByteArray {
- val t0 = System.currentTimeMillis()
-
- // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
- // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
- // noticed as dklen/hlen
- val prf = Mac.getInstance("HmacSHA512")
- prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
-
- // 512 bits key length
val key = ByteArray(64)
- val uc = ByteArray(64)
+ measureTimeMillis {
+ // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
+ // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
+ // noticed as dklen/hlen
+ val prf = Mac.getInstance("HmacSHA512")
+ prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
- // U1 = PRF(Password, Salt || INT_32_BE(i))
- prf.update(salt)
- val int32BE = ByteArray(4) { 0.toByte() }
- int32BE[3] = 1.toByte()
- prf.update(int32BE)
- prf.doFinal(uc, 0)
+ // 512 bits key length
+ val uc = ByteArray(64)
- // copy to the key
- System.arraycopy(uc, 0, key, 0, uc.size)
-
- for (index in 2..iterations) {
- // Uc = PRF(Password, Uc-1)
- prf.update(uc)
+ // U1 = PRF(Password, Salt || INT_32_BE(i))
+ prf.update(salt)
+ val int32BE = ByteArray(4) { 0.toByte() }
+ int32BE[3] = 1.toByte()
+ prf.update(int32BE)
prf.doFinal(uc, 0)
- // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
- for (byteIndex in uc.indices) {
- key[byteIndex] = key[byteIndex] xor uc[byteIndex]
- }
- }
+ // copy to the key
+ System.arraycopy(uc, 0, key, 0, uc.size)
- Timber.v("## deriveKeys() : $iterations in ${System.currentTimeMillis() - t0} ms")
+ for (index in 2..iterations) {
+ // Uc = PRF(Password, Uc-1)
+ prf.update(uc)
+ prf.doFinal(uc, 0)
+
+ // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+ for (byteIndex in uc.indices) {
+ key[byteIndex] = key[byteIndex] xor uc[byteIndex]
+ }
+ }
+ }.also {
+ Timber.v("## deriveKeys() : $iterations in $it ms")
+ }
return key
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index 4947761f05..68a1519670 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.convertFromUTF8
import org.matrix.android.sdk.internal.util.convertToUTF8
+import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException
import org.matrix.olm.OlmMessage
@@ -42,7 +43,6 @@ import org.matrix.olm.OlmOutboundGroupSession
import org.matrix.olm.OlmSession
import org.matrix.olm.OlmUtility
import timber.log.Timber
-import java.net.URLEncoder
import javax.inject.Inject
private val loggerTag = LoggerTag("MXOlmDevice", LoggerTag.CRYPTO)
@@ -55,7 +55,8 @@ internal class MXOlmDevice @Inject constructor(
*/
private val store: IMXCryptoStore,
private val olmSessionStore: OlmSessionStore,
- private val inboundGroupSessionStore: InboundGroupSessionStore
+ private val inboundGroupSessionStore: InboundGroupSessionStore,
+ private val clock: Clock,
) {
val mutex = Mutex()
@@ -277,7 +278,7 @@ internal class MXOlmDevice @Inject constructor(
// Pretend we've received a message at this point, otherwise
// if we try to send a message to the device, it won't use
// this session
- olmSessionWrapper.onMessageReceived()
+ olmSessionWrapper.onMessageReceived(clock.epochMillis())
olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey)
@@ -329,14 +330,6 @@ internal class MXOlmDevice @Inject constructor(
Timber.tag(loggerTag.value).e(e, "## createInboundSession() : removeOneTimeKeys failed")
}
- Timber.tag(loggerTag.value).v("## createInboundSession() : ciphertext: $ciphertext")
- try {
- val sha256 = olmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8"))
- Timber.tag(loggerTag.value).v("## createInboundSession() :ciphertext: SHA256: $sha256")
- } catch (e: Exception) {
- Timber.tag(loggerTag.value).e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
- }
-
val olmMessage = OlmMessage()
olmMessage.mCipherText = ciphertext
olmMessage.mType = messageType.toLong()
@@ -348,7 +341,7 @@ internal class MXOlmDevice @Inject constructor(
val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
// This counts as a received message: set last received message time to now
- olmSessionWrapper.onMessageReceived()
+ olmSessionWrapper.onMessageReceived(clock.epochMillis())
olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
} catch (e: Exception) {
@@ -454,7 +447,7 @@ internal class MXOlmDevice @Inject constructor(
payloadString =
olmSessionWrapper.mutex.withLock {
olmSessionWrapper.olmSession.decryptMessage(olmMessage).also {
- olmSessionWrapper.onMessageReceived()
+ olmSessionWrapper.onMessageReceived(clock.epochMillis())
}
}
olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
@@ -520,6 +513,7 @@ internal class MXOlmDevice @Inject constructor(
return MXOutboundSessionInfo(
sessionId = sessionId,
sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
+ clock,
restoredOutboundGroupSession.creationTime
)
}
@@ -586,6 +580,13 @@ internal class MXOlmDevice @Inject constructor(
// Inbound group session
+ sealed interface AddSessionResult {
+ data class Imported(val ratchetIndex: Int) : AddSessionResult
+ abstract class Failure : AddSessionResult
+ object NotImported : Failure()
+ data class NotImportedHigherIndex(val newIndex: Int) : Failure()
+ }
+
/**
* Add an inbound group session to the session store.
*
@@ -604,7 +605,7 @@ internal class MXOlmDevice @Inject constructor(
senderKey: String,
forwardingCurve25519KeyChain: List,
keysClaimed: Map,
- exportFormat: Boolean): Boolean {
+ exportFormat: Boolean): AddSessionResult {
val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
val existingSession = existingSessionHolder?.wrapper
@@ -612,7 +613,7 @@ internal class MXOlmDevice @Inject constructor(
if (existingSession != null) {
Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
try {
- val existingFirstKnown = existingSession.firstKnownIndex ?: return false.also {
+ val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also {
// This is quite unexpected, could throw if native was released?
Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
candidateSession.olmInboundGroupSession?.releaseSession()
@@ -623,12 +624,12 @@ internal class MXOlmDevice @Inject constructor(
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
candidateSession.olmInboundGroupSession?.releaseSession()
- return false
+ return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
}
} catch (failure: Throwable) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
candidateSession.olmInboundGroupSession?.releaseSession()
- return false
+ return AddSessionResult.NotImported
}
}
@@ -638,19 +639,19 @@ internal class MXOlmDevice @Inject constructor(
val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
if (null == candidateOlmInboundSession) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session ")
- return false
+ return AddSessionResult.NotImported
}
try {
if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
candidateOlmInboundSession.releaseSession()
- return false
+ return AddSessionResult.NotImported
}
} catch (e: Throwable) {
candidateOlmInboundSession.releaseSession()
Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
- return false
+ return AddSessionResult.NotImported
}
candidateSession.senderKey = senderKey
@@ -664,7 +665,7 @@ internal class MXOlmDevice @Inject constructor(
inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
}
- return true
+ return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0)
}
/**
@@ -787,7 +788,7 @@ internal class MXOlmDevice @Inject constructor(
if (timelineSet.contains(messageIndexKey)) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
- Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index 792c9a25dc..8143e36892 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmAccount
import timber.log.Timber
import javax.inject.Inject
@@ -38,6 +39,7 @@ internal class OneTimeKeysUploader @Inject constructor(
private val olmDevice: MXOlmDevice,
private val objectSigner: ObjectSigner,
private val uploadKeysTask: UploadKeysTask,
+ private val clock: Clock,
context: Context
) {
// tell if there is a OTK check in progress
@@ -77,7 +79,7 @@ internal class OneTimeKeysUploader @Inject constructor(
Timber.v("maybeUploadOneTimeKeys: already in progress")
return
}
- if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
+ if (clock.epochMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently.
Timber.v("maybeUploadOneTimeKeys: executed too recently")
return
@@ -94,7 +96,7 @@ internal class OneTimeKeysUploader @Inject constructor(
Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
- lastOneTimeKeyCheck = System.currentTimeMillis()
+ lastOneTimeKeyCheck = clock.epochMillis()
// We then check how many keys we can store in the Account object.
val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
@@ -126,7 +128,7 @@ internal class OneTimeKeysUploader @Inject constructor(
// Check if we need to forget a fallback key
val latestPublishedTime = getLastFallbackKeyPublishTime()
- if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
+ if (latestPublishedTime != 0L && clock.epochMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
// This should be called once you are reasonably certain that you will not receive any more messages
// that use the old fallback key
Timber.d("## forgetFallbackKey()")
@@ -168,7 +170,7 @@ internal class OneTimeKeysUploader @Inject constructor(
olmDevice.markKeysAsPublished()
if (hadUnpublishedFallbackKey) {
// It had an unpublished fallback key that was published just now
- saveLastFallbackKeyPublishTime(System.currentTimeMillis())
+ saveLastFallbackKeyPublishTime(clock.epochMillis())
}
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
deleted file mode 100755
index e6f6ac5053..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.crypto
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
-import org.matrix.android.sdk.internal.di.SessionId
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
-import timber.log.Timber
-import javax.inject.Inject
-
-@SessionScope
-internal class OutgoingGossipingRequestManager @Inject constructor(
- @SessionId private val sessionId: String,
- private val cryptoStore: IMXCryptoStore,
- private val coroutineDispatchers: MatrixCoroutineDispatchers,
- private val cryptoCoroutineScope: CoroutineScope,
- private val gossipingWorkManager: GossipingWorkManager) {
-
- /**
- * Send off a room key request, if we haven't already done so.
- *
- *
- * The `requestBody` is compared (with a deep-equality check) against
- * previous queued or sent requests and if it matches, no change is made.
- * Otherwise, a request is added to the pending list, and a job is started
- * in the background to send it.
- *
- * @param requestBody requestBody
- * @param recipients recipients
- */
- fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>) {
- cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let {
- // Don't resend if it's already done, you need to cancel first (reRequest)
- if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
- Timber.v("## CRYPTO - GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it")
- return@launch
- }
-
- sendOutgoingGossipingRequest(it)
- }
- }
- }
-
- fun sendSecretShareRequest(secretName: String, recipients: Map>) {
- cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- // A bit dirty, but for better stability give other party some time to mark
- // devices trusted :/
- delay(1500)
- cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
- // TODO check if there is already one that is being sent?
- if (it.state == OutgoingGossipingRequestState.SENDING
- /**|| it.state == OutgoingGossipingRequestState.SENT*/
- ) {
- Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it")
- return@launch
- }
-
- sendOutgoingGossipingRequest(it)
- }
- }
- }
-
- /**
- * Cancel room key requests, if any match the given details
- *
- * @param requestBody requestBody
- */
- fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
- cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
- cancelRoomKeyRequest(requestBody, false)
- }
- }
-
- /**
- * Cancel room key requests, if any match the given details, and resend
- *
- * @param requestBody requestBody
- */
- fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
- cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
- cancelRoomKeyRequest(requestBody, true)
- }
- }
-
- /**
- * Cancel room key requests, if any match the given details, and resend
- *
- * @param requestBody requestBody
- * @param andResend true to resend the key request
- */
- private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) {
- val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody) // no request was made for this key
- ?: return Unit.also {
- Timber.v("## CRYPTO - GOSSIP cancelRoomKeyRequest() Unknown request $requestBody")
- }
-
- sendOutgoingRoomKeyRequestCancellation(req, andResend)
- }
-
- /**
- * Send the outgoing key request.
- *
- * @param request the request
- */
- private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) {
- Timber.v("## CRYPTO - GOSSIP sendOutgoingGossipingRequest() : Requesting keys $request")
-
- val params = SendGossipRequestWorker.Params(
- sessionId = sessionId,
- keyShareRequest = request as? OutgoingRoomKeyRequest,
- secretShareRequest = request as? OutgoingSecretRequest,
- txnId = createUniqueTxnId()
- )
- cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING)
- val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
- gossipingWorkManager.postWork(workRequest)
- }
-
- /**
- * Given a OutgoingRoomKeyRequest, cancel it and delete the request record
- *
- * @param request the request
- */
- private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest, resend: Boolean = false) {
- Timber.v("## CRYPTO - sendOutgoingRoomKeyRequestCancellation $request")
- val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request)
- cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING)
-
- val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
- gossipingWorkManager.postWork(workRequest)
-
- if (resend) {
- val reSendParams = SendGossipRequestWorker.Params(
- sessionId = sessionId,
- keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()),
- txnId = createUniqueTxnId()
- )
- val reSendWorkRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(reSendParams), true)
- gossipingWorkManager.postWork(reSendWorkRequest)
- }
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
new file mode 100755
index 0000000000..09a9868428
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
+import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
+import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
+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.content.EncryptedEventContent
+import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.util.fromBase64
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.di.SessionId
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
+import timber.log.Timber
+import java.util.Stack
+import java.util.concurrent.Executors
+import javax.inject.Inject
+import kotlin.system.measureTimeMillis
+
+private val loggerTag = LoggerTag("OutgoingKeyRequestManager", LoggerTag.CRYPTO)
+
+/**
+ * This class is responsible for sending key requests to other devices when a message failed to decrypt.
+ * It's lifecycle is based on the sync pulse:
+ * - You can post queries for session, or report when you got a session
+ * - At the end of the sync (onSyncComplete) it will then process all the posted request and send to devices
+ * If a request failed it will be retried at the end of the next sync
+ */
+@SessionScope
+internal class OutgoingKeyRequestManager @Inject constructor(
+ @SessionId private val sessionId: String,
+ @UserId private val myUserId: String,
+ private val cryptoStore: IMXCryptoStore,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val cryptoConfig: MXCryptoConfig,
+ private val inboundGroupSessionStore: InboundGroupSessionStore,
+ private val sendToDeviceTask: SendToDeviceTask,
+ private val deviceListManager: DeviceListManager,
+ private val perSessionBackupQueryRateLimiter: PerSessionBackupQueryRateLimiter) {
+
+ private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+ private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher)
+ private val sequencer = SemaphoreCoroutineSequencer()
+
+ // We only have one active key request per session, so we don't request if it's already requested
+ // But it could make sense to check more the backup, as it's evolving.
+ // We keep a stack as we consider that the key requested last is more likely to be on screen?
+ private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack>()
+
+ fun requestKeyForEvent(event: Event, force: Boolean) {
+ val (targets, body) = getRoomKeyRequestTargetForEvent(event) ?: return
+ val index = ratchetIndexForMessage(event) ?: 0
+ postRoomKeyRequest(body, targets, index, force)
+ }
+
+ private fun getRoomKeyRequestTargetForEvent(event: Event): Pair