From 4aaf22832f21451169d19b58b852ea24e21adddc Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 3 Jun 2024 17:57:20 +0200 Subject: [PATCH 1/3] Fix | Share room keys with dehydrated devices --- .../internal/crypto/model/rest/DeviceKeysWithUnsigned.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt index 32f577c99b..ca72c0819c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt @@ -58,5 +58,11 @@ internal data class DeviceKeysWithUnsigned( * Additional data added to the device key information by intermediate servers, and not covered by the signatures. */ @Json(name = "unsigned") - val unsigned: UnsignedDeviceInfo? = null + val unsigned: UnsignedDeviceInfo? = null, + + /** + * Optional property `dehydrated`, which is set to true for dehydrated devices. + */ + @Json(name = "dehydrated") + val dehydrated: Boolean? = null, ) From 90aafbc6bd668ec40a47d5a058197b410cf203b5 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 3 Jun 2024 17:59:20 +0200 Subject: [PATCH 2/3] Add changelog --- changelog.d/8842.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8842.bugfix diff --git a/changelog.d/8842.bugfix b/changelog.d/8842.bugfix new file mode 100644 index 0000000000..77c2b3eed7 --- /dev/null +++ b/changelog.d/8842.bugfix @@ -0,0 +1 @@ +Element-Android session doesn't encrypt for a dehydrated device From ad9f9fb19329bd2ce2e42c9f4052319eabee7b29 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 5 Jun 2024 10:37:41 +0200 Subject: [PATCH 3/3] Accept and pass any deviceKey payload to rust --- .../internal/crypto/model/CryptoInfoMapper.kt | 13 --- .../model/rest/DeviceKeysWithUnsigned.kt | 68 ------------ .../crypto/model/rest/KeysQueryResponse.kt | 2 +- .../crypto/tasks/DownloadKeysForUsersTask.kt | 3 +- .../internal/crypto/KeysQueryResponseTest.kt | 103 ++++++++++++++++++ 5 files changed, 105 insertions(+), 84 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt index de9b3f24ff..119372ad32 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt @@ -18,23 +18,10 @@ package org.matrix.android.sdk.internal.crypto.model import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo internal object CryptoInfoMapper { - fun map(deviceKeysWithUnsigned: DeviceKeysWithUnsigned): CryptoDeviceInfo { - return CryptoDeviceInfo( - deviceId = deviceKeysWithUnsigned.deviceId, - userId = deviceKeysWithUnsigned.userId, - algorithms = deviceKeysWithUnsigned.algorithms, - keys = deviceKeysWithUnsigned.keys, - signatures = deviceKeysWithUnsigned.signatures, - unsigned = deviceKeysWithUnsigned.unsigned, - trustLevel = null - ) - } - fun map(cryptoDeviceInfo: CryptoDeviceInfo): DeviceKeys { return DeviceKeys( deviceId = cryptoDeviceInfo.deviceId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt deleted file mode 100644 index ca72c0819c..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt +++ /dev/null @@ -1,68 +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.model.rest - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo - -@JsonClass(generateAdapter = true) -internal data class DeviceKeysWithUnsigned( - /** - * Required. The ID of the user the device belongs to. Must match the user ID used when logging in. - */ - @Json(name = "user_id") - val userId: String, - - /** - * Required. The ID of the device these keys belong to. Must match the device ID used when logging in. - */ - @Json(name = "device_id") - val deviceId: String, - - /** - * Required. The encryption algorithms supported by this device. - */ - @Json(name = "algorithms") - val algorithms: List?, - - /** - * Required. Public identity keys. The names of the properties should be in the format :. - * The keys themselves should be encoded as specified by the key algorithm. - */ - @Json(name = "keys") - val keys: Map?, - - /** - * Required. Signatures for the device key object. A map from user ID, to a map from : to the signature. - * The signature is calculated using the process described at https://matrix.org/docs/spec/appendices.html#signing-json. - */ - @Json(name = "signatures") - val signatures: Map>?, - - /** - * Additional data added to the device key information by intermediate servers, and not covered by the signatures. - */ - @Json(name = "unsigned") - val unsigned: UnsignedDeviceInfo? = null, - - /** - * Optional property `dehydrated`, which is set to true for dehydrated devices. - */ - @Json(name = "dehydrated") - val dehydrated: Boolean? = null, -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt index a099419c3c..ce10af8c67 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt @@ -34,7 +34,7 @@ internal data class KeysQueryResponse( * For each device, the information returned will be the same as uploaded via /keys/upload, with the addition of an unsigned property. */ @Json(name = "device_keys") - val deviceKeys: Map>? = null, + val deviceKeys: Map>>? = null, /** * If any remote homeservers could not be reached, they are recorded here. The names of the diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt index 70af859ddb..8fd943afcc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo @@ -52,7 +51,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( return if (bestChunkSize.shouldChunk()) { // Store server results in these mutable maps - val deviceKeys = mutableMapOf>() + val deviceKeys = mutableMapOf>>() val failures = mutableMapOf>() val masterKeys = mutableMapOf() val selfSigningKeys = mutableMapOf() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt new file mode 100644 index 0000000000..d4bf2b9e70 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt @@ -0,0 +1,103 @@ +/* + * 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 org.amshove.kluent.internal.assertEquals +import org.junit.Test +import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse +import org.matrix.android.sdk.internal.di.MoshiProvider + +class KeysQueryResponseTest { + + private val moshi = MoshiProvider.providesMoshi() + private val keysQueryResponseAdapter = moshi.adapter(KeysQueryResponse::class.java) + + private fun aKwysQueryResponseWithDehydrated(): KeysQueryResponse { + val rawResponseWithDehydratedDevice = """ + { + "device_keys": { + "@dehydration2:localhost": { + "TDHZGMDVNO": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "TDHZGMDVNO", + "keys": { + "curve25519:TDHZGMDVNO": "ClMOrHlQJqaqr4oESYyPURwD4BSQxMlEZZk/AnYxVSk", + "ed25519:TDHZGMDVNO": "5iZ4zfk0URyIH8YOIWnXmJo41Vn34IixGYphkMdDzik" + }, + "signatures": { + "@dehydration2:localhost": { + "ed25519:TDHZGMDVNO": "O6VP+ELiCVAJGHaRdReKga0LGMQahjRnp4znZH7iJO6maZV8aSXnpugSoVsSPRvQ4GBkjX+KXAXU+ODZ0J8MDg", + "ed25519:YZ0EmlbDX+t/m/MB5EWkQLw8cEDg7hX4Zy9699h3hd8": "lG3idYliFGOAe4F/7tENIQ6qI0d41VQKY34BHyVvvWKbv63zDDO5kBTwBeXfUSEeRqyxET3SXLXfB1D8E8LUDg" + } + }, + "user_id": "@dehydration2:localhost", + "unsigned": { + "device_display_name": "localhost:8080: Chrome on macOS" + } + }, + "Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "dehydrated": true, + "device_id": "Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ", + "keys": { + "curve25519:Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": "Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ", + "ed25519:Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": "sVY5Xq13sIdhC4We/p5CH69++GsIWRNUhHijtucBirs" + }, + "signatures": { + "@dehydration2:localhost": { + "ed25519:Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": "e2aVrdnD/kor2T0Ok/4SC32MW4WB5JXFSd2wnXV8apxFJBfbdZErANiUbo1Zz/HAasaXM5NBfkr/9gVTdph9BQ", + "ed25519:YZ0EmlbDX+t/m/MB5EWkQLw8cEDg7hX4Zy9699h3hd8": "rVzeE1LbB12XOlckxjRLjt3eq2jVlek6OJ4p08+8g8CMoiJDcw1OVzbJuG/8u6ryarxQF6Yqr4Xu2TqCPBmHDw" + } + }, + "user_id": "@dehydration2:localhost", + "unsigned": { + "device_display_name": "Dehydrated device" + } + } + } + } + } + """.trimIndent() + + return keysQueryResponseAdapter.fromJson(rawResponseWithDehydratedDevice)!! + } + + @Test + fun `Should parse correctly devices with new dehydrated field`() { + val aKeysQueryResponse = aKwysQueryResponseWithDehydrated() + + val pojoToJson = keysQueryResponseAdapter.toJson(aKeysQueryResponse) + + val rawAdapter = moshi.adapter(Map::class.java) + + val rawJson = rawAdapter.fromJson(pojoToJson)!! + + val deviceKeys = (rawJson["device_keys"] as Map<*, *>)["@dehydration2:localhost"] as Map<*, *> + + assertEquals(deviceKeys.keys.size, 2) + + val dehydratedDevice = deviceKeys["Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ"] as Map<*, *> + + assertEquals(dehydratedDevice["dehydrated"] as? Boolean, true) + } +}