diff --git a/CHANGES.md b/CHANGES.md index d2d1902205..654f2e4fa4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,6 +48,7 @@ Changes in RiotX 0.9.0 (2019-12-05) Features ✨: - Account creation. It's now possible to create account on any homeserver with RiotX (#34) - Iteration of the login flow (#613) + - [SDK] MSC2241 / verification in DMs (#707) Improvements 🙌: - Send mention Pills from composer diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 88c0787b4d..902baae06f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -16,19 +16,42 @@ package im.vector.matrix.android.api.session.crypto.sas +import im.vector.matrix.android.api.MatrixCallback + +/** + * https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework + * + * Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors. + * SAS verification is a user-friendly key verification process. + * SAS verification is intended to be a highly interactive process for users, + * and as such exposes verification methods which are easier for users to use. + */ interface SasVerificationService { + fun addListener(listener: SasVerificationListener) fun removeListener(listener: SasVerificationListener) + /** + * Mark this device as verified manually + */ fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction? + /** + * Shortcut for KeyVerificationStart.VERIF_METHOD_SAS + * @see beginKeyVerification + */ fun beginKeyVerificationSAS(userId: String, deviceID: String): String? + /** + * Request a key verification from another user using toDevice events. + */ fun beginKeyVerification(method: String, userId: String, deviceID: String): String? + fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) + // fun transactionUpdated(tx: SasVerificationTransaction) interface SasVerificationListener { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt index d24ccadb55..9610daf294 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.crypto.sas interface SasVerificationTransaction { - val state: SasVerificationTxState + var state: SasVerificationTxState val cancelledReason: CancelCode? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 38c24fa89b..a8c6aed44e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -72,6 +72,7 @@ object EventType { const val KEY_VERIFICATION_KEY = "m.key.verification.key" const val KEY_VERIFICATION_MAC = "m.key.verification.mac" const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel" + const val KEY_VERIFICATION_DONE = "m.key.verification.done" // Relation Events const val REACTION = "m.reaction" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt new file mode 100644 index 0000000000..ec773916fd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +internal data class MessageRelationContent( + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt index 8cef40f21a..d4e6d5ea71 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt @@ -26,6 +26,7 @@ object MessageType { const val MSGTYPE_VIDEO = "m.video" const val MSGTYPE_LOCATION = "m.location" const val MSGTYPE_FILE = "m.file" + const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request" const val FORMAT_MATRIX_HTML = "org.matrix.custom.html" // Add, in local, a fake message type in order to StickerMessage can inherit Message class // Because sticker isn't a message type but a event type without msgtype field diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt new file mode 100644 index 0000000000..bda4b9f0ac --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory +import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept +import timber.log.Timber + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationAcceptContent( + @Json(name = "hash") override val hash: String?, + @Json(name = "key_agreement_protocol") override val keyAgreementProtocol: String?, + @Json(name = "message_authentication_code") override val messageAuthenticationCode: String?, + @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?, + @Json(name = "commitment") override var commitment: String? = null +) : VerifInfoAccept { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() + || keyAgreementProtocol.isNullOrBlank() + || hash.isNullOrBlank() + || commitment.isNullOrBlank() + || messageAuthenticationCode.isNullOrBlank() + || shortAuthenticationStrings.isNullOrEmpty()) { + Timber.e("## received invalid verification request") + return false + } + return true + } + + override fun toEventContent() = this.toContent() + + companion object : AcceptVerifInfoFactory { + + override fun create(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept { + return MessageVerificationAcceptContent( + hash, + keyAgreementProtocol, + messageAuthenticationCode, + shortAuthenticationStrings, + RelationDefaultContent( + RelationType.REFERENCE, + tid + ), + commitment + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt new file mode 100644 index 0000000000..08fc3cbdbb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationCancelContent( + @Json(name = "code") override val code: String? = null, + @Json(name = "reason") override val reason: String? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? + +) : VerifInfoCancel { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun toEventContent() = this.toContent() + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || code.isNullOrBlank()) { + return false + } + return true + } + + companion object { + fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent { + return MessageVerificationCancelContent( + reason.value, + reason.humanReadable, + RelationDefaultContent( + RelationType.REFERENCE, + transactionId + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt new file mode 100644 index 0000000000..965fcb79bb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerificationInfo + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationDoneContent( + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerificationInfo { + override fun isValid() = true +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt new file mode 100644 index 0000000000..0b93e3299a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey +import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory +import timber.log.Timber + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationKeyContent( + /** + * The device’s ephemeral public key, as an unpadded base64 string + */ + @Json(name = "key") override val key: String? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerifInfoKey { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || key.isNullOrBlank()) { + Timber.e("## received invalid verification request") + return false + } + return true + } + + override fun toEventContent() = this.toContent() + + companion object : KeyVerifInfoFactory { + + override fun create(tid: String, pubKey: String): VerifInfoKey { + return MessageVerificationKeyContent( + pubKey, + RelationDefaultContent( + RelationType.REFERENCE, + tid + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt new file mode 100644 index 0000000000..92ea4bca52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationMacContent( + @Json(name = "mac") override val mac: Map? = null, + @Json(name = "keys") override val keys: String? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerifInfoMac { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun toEventContent() = this.toContent() + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) { + return false + } + return true + } + + companion object : VerifInfoMacFactory { + override fun create(tid: String, mac: Map, keys: String): VerifInfoMac { + return MessageVerificationMacContent( + mac, + keys, + RelationDefaultContent( + RelationType.REFERENCE, + tid + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt new file mode 100644 index 0000000000..afefa39847 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +class MessageVerificationRequestContent( + @Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, + @Json(name = "body") override val body: String, + @Json(name = "from_device") val fromDevice: String, + @Json(name = "methods") val methods: List, + @Json(name = "to") val to: String, + // @Json(name = "timestamp") val timestamp: Int, + @Json(name = "format") val format: String? = null, + @Json(name = "formatted_body") val formattedBody: String? = null, + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null +) : MessageContent diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt new file mode 100644 index 0000000000..f6ec00ffb2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.crypto.sas.SasMode +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction +import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart +import im.vector.matrix.android.internal.util.JsonCanonicalizer +import timber.log.Timber + +@JsonClass(generateAdapter = true) +data class MessageVerificationStartContent( + @Json(name = "from_device") override val fromDevice: String?, + @Json(name = "hashes") override val hashes: List?, + @Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List?, + @Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List?, + @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, + @Json(name = "method") override val method: String?, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerifInfoStart { + + override fun toCanonicalJson(): String? { + return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this) + } + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun isValid(): Boolean { + if ( + (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() || method != KeyVerificationStart.VERIF_METHOD_SAS || keyAgreementProtocols.isNullOrEmpty() || hashes.isNullOrEmpty()) + || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() + || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) + || shortAuthenticationStrings.isNullOrEmpty() + || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + Timber.e("## received invalid verification request") + return false + } + return true + } + + override fun toEventContent() = this.toContent() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index a12f6e40ce..4243c6a464 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -177,6 +177,9 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask + @Binds + abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask + @Binds abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice) : ClaimOneTimeKeysForUsersDeviceTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index c50b9e2e10..58ab8dda32 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -136,6 +136,10 @@ internal class DefaultCryptoService @Inject constructor( private val cryptoCoroutineScope: CoroutineScope ) : CryptoService { + init { + sasVerificationService.cryptoService = this + } + private val uiHandler = Handler(Looper.getMainLooper()) // MXEncrypting instance for each room. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt index 7be6f2042c..20d7682cb9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt @@ -17,13 +17,15 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept +import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory import timber.log.Timber /** * Sent by Bob to accept a verification from a previously sent m.key.verification.start message. */ @JsonClass(generateAdapter = true) -data class KeyVerificationAccept( +internal data class KeyVerificationAccept( /** * string to identify the transaction. @@ -31,39 +33,41 @@ data class KeyVerificationAccept( * Alice’s device should record this ID and use it in future messages in this transaction. */ @Json(name = "transaction_id") - var transactionID: String? = null, + override var transactionID: String? = null, /** * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device */ @Json(name = "key_agreement_protocol") - var keyAgreementProtocol: String? = null, + override var keyAgreementProtocol: String? = null, /** * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device */ - var hash: String? = null, + @Json(name = "hash") + override var hash: String? = null, /** * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device */ @Json(name = "message_authentication_code") - var messageAuthenticationCode: String? = null, + override var messageAuthenticationCode: String? = null, /** * An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device */ @Json(name = "short_authentication_string") - var shortAuthenticationStrings: List? = null, + override var shortAuthenticationStrings: List? = null, /** * The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64) * and the canonical JSON representation of the m.key.verification.start message. */ - var commitment: String? = null -) : SendToDeviceObject { + @Json(name = "commitment") + override var commitment: String? = null +) : SendToDeviceObject, VerifInfoAccept { - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || keyAgreementProtocol.isNullOrBlank() || hash.isNullOrBlank() @@ -76,13 +80,15 @@ data class KeyVerificationAccept( return true } - companion object { - fun create(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): KeyVerificationAccept { + override fun toSendToDeviceObject() = this + + companion object : AcceptVerifInfoFactory { + override fun create(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept { return KeyVerificationAccept().apply { this.transactionID = tid this.keyAgreementProtocol = keyAgreementProtocol diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt index b5c45e9566..7ffffbbfa1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt @@ -18,40 +18,43 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel /** * To device event sent by either party to cancel a key verification. */ @JsonClass(generateAdapter = true) -data class KeyVerificationCancel( +internal data class KeyVerificationCancel( /** * the transaction ID of the verification to cancel */ @Json(name = "transaction_id") - var transactionID: String? = null, + override val transactionID: String? = null, /** * machine-readable reason for cancelling, see #CancelCode */ - var code: String? = null, + override var code: String? = null, /** * human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given. */ - var reason: String? = null -) : SendToDeviceObject { + override var reason: String? = null +) : SendToDeviceObject, VerifInfoCancel { companion object { fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel { - return KeyVerificationCancel().apply { - this.transactionID = tid - this.code = cancelCode.value - this.reason = cancelCode.humanReadable - } + return KeyVerificationCancel( + tid, + cancelCode.value, + cancelCode.humanReadable + ) } } - fun isValid(): Boolean { + override fun toSendToDeviceObject() = this + + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || code.isNullOrBlank()) { return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt index 4c6243fee3..458c12743f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt @@ -17,37 +17,33 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory +import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey /** * Sent by both devices to send their ephemeral Curve25519 public key to the other device. */ @JsonClass(generateAdapter = true) -data class KeyVerificationKey( +internal data class KeyVerificationKey( /** * the ID of the transaction that the message is part of */ - @Json(name = "transaction_id") - @JvmField - var transactionID: String? = null, + @Json(name = "transaction_id") override var transactionID: String? = null, /** * The device’s ephemeral public key, as an unpadded base64 string */ - @JvmField - var key: String? = null + @Json(name = "key") override val key: String? = null -) : SendToDeviceObject { +) : SendToDeviceObject, VerifInfoKey { - companion object { - fun create(tid: String, key: String): KeyVerificationKey { - return KeyVerificationKey().apply { - this.transactionID = tid - this.key = key - } + companion object : KeyVerifInfoFactory { + override fun create(tid: String, pubKey: String): KeyVerificationKey { + return KeyVerificationKey(tid, pubKey) } } - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || key.isNullOrBlank()) { return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt index 8732e366d2..d2c147e145 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt @@ -17,49 +17,32 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory /** * Sent by both devices to send the MAC of their device key to the other device. */ @JsonClass(generateAdapter = true) -data class KeyVerificationMac( - /** - * the ID of the transaction that the message is part of - */ - @Json(name = "transaction_id") - var transactionID: String? = null, +internal data class KeyVerificationMac( + @Json(name = "transaction_id") override val transactionID: String? = null, + @Json(name = "mac") override val mac: Map? = null, + @Json(name = "key") override val keys: String? = null - /** - * A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key - */ - @JvmField - var mac: Map? = null, +) : SendToDeviceObject, VerifInfoMac { - /** - * The MAC of the comma-separated, sorted list of key IDs given in the mac property, - * as an unpadded base64 string, calculated using the MAC key. - * For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will - * give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”. - */ - @JvmField - var keys: String? = null - -) : SendToDeviceObject { - - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) { return false } return true } - companion object { - fun create(tid: String, mac: Map, keys: String): KeyVerificationMac { - return KeyVerificationMac().apply { - this.transactionID = tid - this.mac = mac - this.keys = keys - } + override fun toSendToDeviceObject(): SendToDeviceObject? = this + + companion object : VerifInfoMacFactory { + override fun create(tid: String, mac: Map, keys: String): VerifInfoMac { + return KeyVerificationMac(tid, mac, keys) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt new file mode 100644 index 0000000000..14954a17cd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerificationInfo + +/** + * Requests a key verification with another user's devices. + */ +@JsonClass(generateAdapter = true) +data class KeyVerificationRequest( + + @Json(name = "from_device") + val fromDevice: String, + /** The verification methods supported by the sender. */ + val methods: List = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + /** + * The POSIX timestamp in milliseconds for when the request was made. + * If the request is in the future by more than 5 minutes or more than 10 minutes in the past, + * the message should be ignored by the receiver. + */ + val timestamp: Int, + + @Json(name = "transaction_id") + var transactionID: String? = null + +) : SendToDeviceObject, VerificationInfo { + + override fun isValid(): Boolean { + // TODO + return true + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index 081b19161a..f7cc10a12b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -19,21 +19,27 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction +import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart +import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber /** * Sent by Alice to initiate an interactive key verification. */ @JsonClass(generateAdapter = true) -class KeyVerificationStart : SendToDeviceObject { +class KeyVerificationStart : SendToDeviceObject, VerifInfoStart { + + override fun toCanonicalJson(): String? { + return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) + } /** * Alice’s device ID */ @Json(name = "from_device") - var fromDevice: String? = null + override var fromDevice: String? = null - var method: String? = null + override var method: String? = null /** * String to identify the transaction. @@ -41,7 +47,7 @@ class KeyVerificationStart : SendToDeviceObject { * Alice’s device should record this ID and use it in future messages in this transaction. */ @Json(name = "transaction_id") - var transactionID: String? = null + override var transactionID: String? = null /** * An array of key agreement protocols that Alice’s client understands. @@ -49,13 +55,13 @@ class KeyVerificationStart : SendToDeviceObject { * Other methods may be defined in the future */ @Json(name = "key_agreement_protocols") - var keyAgreementProtocols: List? = null + override var keyAgreementProtocols: List? = null /** * An array of hashes that Alice’s client understands. * Must include “sha256”. Other methods may be defined in the future. */ - var hashes: List? = null + override var hashes: List? = null /** * An array of message authentication codes that Alice’s client understands. @@ -63,7 +69,7 @@ class KeyVerificationStart : SendToDeviceObject { * Other methods may be defined in the future. */ @Json(name = "message_authentication_codes") - var messageAuthenticationCodes: List? = null + override var messageAuthenticationCodes: List? = null /** * An array of short authentication string methods that Alice’s client (and Alice) understands. @@ -72,13 +78,13 @@ class KeyVerificationStart : SendToDeviceObject { * Other methods may be defined in the future */ @Json(name = "short_authentication_string") - var shortAuthenticationStrings: List? = null + override var shortAuthenticationStrings: List? = null companion object { const val VERIF_METHOD_SAS = "m.sas.v1" } - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() || method != VERIF_METHOD_SAS @@ -95,4 +101,6 @@ class KeyVerificationStart : SendToDeviceObject { } return true } + + override fun toSendToDeviceObject() = this } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt new file mode 100644 index 0000000000..951bc6385a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.tasks + +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitCallback +import javax.inject.Inject + +internal interface EncryptEventTask : Task { + data class Params(val roomId: String, + val event: Event, + /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ + val keepKeys: List? = null, + val crypto: CryptoService + ) +} + +internal class DefaultEncryptEventTask @Inject constructor( +// private val crypto: CryptoService + private val localEchoUpdater: LocalEchoUpdater +) : EncryptEventTask { + override suspend fun execute(params: EncryptEventTask.Params): Event { + if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event + val localEvent = params.event + if (localEvent.eventId == null) { + throw IllegalArgumentException() + } + + localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING) + + val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf() + params.keepKeys?.forEach { + localMutableContent.remove(it) + } + +// try { + awaitCallback { + params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it) + }.let { result -> + val modifiedContent = HashMap(result.eventContent) + params.keepKeys?.forEach { toKeep -> + localEvent.content?.get(toKeep)?.let { + // put it back in the encrypted thing + modifiedContent[toKeep] = it + } + } + val safeResult = result.copy(eventContent = modifiedContent) + return localEvent.copy( + type = safeResult.eventType, + content = safeResult.eventContent + ) + } +// } catch (throwable: Throwable) { +// val sendState = when (throwable) { +// is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES +// else -> SendState.UNDELIVERED +// } +// localEchoUpdater.updateSendState(localEvent.eventId, sendState) +// throw throwable +// } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt new file mode 100644 index 0000000000..57d225a193 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.tasks + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface RequestVerificationDMTask : Task { + data class Params( + val roomId: String, + val from: String, + val methods: List, + val to: String, + val cryptoService: CryptoService + ) +} + +internal class DefaultRequestVerificationDMTask @Inject constructor( + private val localEchoUpdater: LocalEchoUpdater, + private val localEchoEventFactory: LocalEchoEventFactory, + private val encryptEventTask: DefaultEncryptEventTask, + private val monarchy: Monarchy, + private val roomAPI: RoomAPI) + : RequestVerificationDMTask { + + override suspend fun execute(params: RequestVerificationDMTask.Params): SendResponse { + val event = createRequestEvent(params) + val localID = event.eventId!! + + try { + localEchoUpdater.updateSendState(localID, SendState.SENDING) + val executeRequest = executeRequest { + apiCall = roomAPI.send( + localID, + roomId = params.roomId, + content = event.content, + eventType = event.type // message or room.encrypted + ) + } + localEchoUpdater.updateSendState(localID, SendState.SENT) + return executeRequest + } catch (e: Throwable) { + localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED) + throw e + } + } + + private suspend fun createRequestEvent(params: RequestVerificationDMTask.Params): Event { + val event = localEchoEventFactory.createVerificationRequest(params.roomId, params.from, params.to, params.methods).also { + localEchoEventFactory.saveLocalEcho(monarchy, it) + } + if (params.cryptoService.isRoomEncrypted(params.roomId)) { + try { + return encryptEventTask.execute(EncryptEventTask.Params( + params.roomId, + event, + listOf("m.relates_to"), + params.cryptoService + )) + } catch (throwable: Throwable) { + // We said it's ok to send verification request in clear + } + } + return event + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt new file mode 100644 index 0000000000..b850a1a1e6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.tasks + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface SendVerificationMessageTask : Task { + data class Params( + val type: String, + val roomId: String, + val content: Content, + val cryptoService: CryptoService? + ) +} + +internal class DefaultSendVerificationMessageTask @Inject constructor( + private val localEchoUpdater: LocalEchoUpdater, + private val localEchoEventFactory: LocalEchoEventFactory, + private val encryptEventTask: DefaultEncryptEventTask, + private val monarchy: Monarchy, + @UserId private val userId: String, + private val roomAPI: RoomAPI) : SendVerificationMessageTask { + + override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse { + val event = createRequestEvent(params) + val localID = event.eventId!! + + try { + localEchoUpdater.updateSendState(localID, SendState.SENDING) + val executeRequest = executeRequest { + apiCall = roomAPI.send( + localID, + roomId = params.roomId, + content = event.content, + eventType = event.type + ) + } + localEchoUpdater.updateSendState(localID, SendState.SENT) + return executeRequest + } catch (e: Throwable) { + localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED) + throw e + } + } + + private suspend fun createRequestEvent(params: SendVerificationMessageTask.Params): Event { + val localID = LocalEcho.createLocalEchoId() + val event = Event( + roomId = params.roomId, + originServerTs = System.currentTimeMillis(), + senderId = userId, + eventId = localID, + type = params.type, + content = params.content, + unsignedData = UnsignedData(age = null, transactionId = localID) + ).also { + localEchoEventFactory.saveLocalEcho(monarchy, it) + } + + if (params.cryptoService?.isRoomEncrypted(params.roomId) == true) { + try { + return encryptEventTask.execute(EncryptEventTask.Params( + params.roomId, + event, + listOf("m.relates_to"), + params.cryptoService + )) + } catch (throwable: Throwable) { + // We said it's ok to send verification request in clear + } + } + return event + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt similarity index 79% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt index 6ed5be4881..aac2b49f57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.crypto.verification import android.util.Base64 +import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction @@ -23,33 +24,20 @@ import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber -internal class IncomingSASVerificationTransaction( - private val sasVerificationService: DefaultSasVerificationService, - private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val credentials: Credentials, +internal class DefaultIncomingSASVerificationTransaction( + setDeviceVerificationAction: SetDeviceVerificationAction, + override val credentials: Credentials, private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor, deviceFingerprint: String, transactionId: String, - otherUserID: String) - : SASVerificationTransaction( - sasVerificationService, + otherUserID: String +) : SASVerificationTransaction( setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, deviceFingerprint, transactionId, otherUserID, @@ -78,10 +66,10 @@ internal class IncomingSASVerificationTransaction( } } - override fun onVerificationStart(startReq: KeyVerificationStart) { - Timber.v("## SAS received verification request from state $state") + override fun onVerificationStart(startReq: VerifInfoStart) { + Timber.v("## SAS I: received verification request from state $state") if (state != SasVerificationTxState.None) { - Timber.e("## received verification request from invalid state") + Timber.e("## SAS I: received verification request from invalid state") // should I cancel?? throw IllegalStateException("Interactive Key verification already started") } @@ -92,7 +80,7 @@ internal class IncomingSASVerificationTransaction( override fun performAccept() { if (state != SasVerificationTxState.OnStarted) { - Timber.e("## Cannot perform accept from state $state") + Timber.e("## SAS Cannot perform accept from state $state") return } @@ -109,7 +97,7 @@ internal class IncomingSASVerificationTransaction( if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() } || agreedShortCode.isNullOrEmpty()) { // Failed to find agreement - Timber.e("## Failed to find agreement ") + Timber.e("## SAS Failed to find agreement ") cancel(CancelCode.UnknownMethod) return } @@ -118,15 +106,15 @@ internal class IncomingSASVerificationTransaction( val mxDeviceInfo = cryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId) if (mxDeviceInfo?.fingerprint() == null) { - Timber.e("## Failed to find device key ") + Timber.e("## SAS Failed to find device key ") // TODO force download keys!! // would be probably better to download the keys // for now I cancel cancel(CancelCode.User) } else { - // val otherKey = info.identityKey() + // val otherKey = info.identityKey() // need to jump back to correct thread - val accept = KeyVerificationAccept.create( + val accept = transport.createAccept( tid = transactionId, keyAgreementProtocol = agreedProtocol!!, hash = agreedHash!!, @@ -138,13 +126,13 @@ internal class IncomingSASVerificationTransaction( } } - private fun doAccept(accept: KeyVerificationAccept) { + private fun doAccept(accept: VerifInfoAccept) { this.accepted = accept - Timber.v("## SAS accept request id:$transactionId") + Timber.v("## SAS incoming accept request id:$transactionId") // The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB, // concatenated with the canonical JSON representation of the content of the m.key.verification.start message - val concat = getSAS().publicKey + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!) + val concat = getSAS().publicKey + startReq!!.toCanonicalJson() accept.commitment = hashUsingAgreedHashMethod(concat) ?: "" // we need to send this to other device now state = SasVerificationTxState.SendingAccept @@ -156,15 +144,15 @@ internal class IncomingSASVerificationTransaction( } } - override fun onVerificationAccept(accept: KeyVerificationAccept) { + override fun onVerificationAccept(accept: VerifInfoAccept) { Timber.v("## SAS invalid message for incoming request id:$transactionId") cancel(CancelCode.UnexpectedMessage) } - override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) { + override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) { Timber.v("## SAS received key for request id:$transactionId") if (state != SasVerificationTxState.SendingAccept && state != SasVerificationTxState.Accepted) { - Timber.e("## received key from invalid state $state") + Timber.e("## SAS received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } @@ -175,7 +163,7 @@ internal class IncomingSASVerificationTransaction( // sending Bob’s public key QB val pubKey = getSAS().publicKey - val keyToDevice = KeyVerificationKey.create(transactionId, pubKey) + val keyToDevice = transport.createKey(transactionId, pubKey) // we need to send this to other device now state = SasVerificationTxState.SendingKey this.sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) { @@ -206,14 +194,16 @@ internal class IncomingSASVerificationTransaction( // emoji: generate six bytes by using HKDF. shortCodeBytes = getSAS().generateShortCode(sasInfo, 6) - Timber.e("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}") - Timber.e("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}") + if (BuildConfig.LOG_PRIVATE_DATA) { + Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}") + Timber.v("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}") + } state = SasVerificationTxState.ShortCodeReady } - override fun onKeyVerificationMac(vKey: KeyVerificationMac) { - Timber.v("## SAS received mac for request id:$transactionId") + override fun onKeyVerificationMac(vKey: VerifInfoMac) { + Timber.v("## SAS I: received mac for request id:$transactionId") // Check for state? if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent @@ -221,7 +211,7 @@ internal class IncomingSASVerificationTransaction( && state != SasVerificationTxState.ShortCodeAccepted && state != SasVerificationTxState.SendingMac && state != SasVerificationTxState.MacSent) { - Timber.e("## received key from invalid state $state") + Timber.e("## SAS I: received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt similarity index 79% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt index cade637cce..4362e897c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt @@ -21,34 +21,22 @@ import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRe import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber -internal class OutgoingSASVerificationRequest( - private val sasVerificationService: DefaultSasVerificationService, - private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val credentials: Credentials, - private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor, +internal class DefaultOutgoingSASVerificationRequest( + setDeviceVerificationAction: SetDeviceVerificationAction, + credentials: Credentials, + cryptoStore: IMXCryptoStore, deviceFingerprint: String, transactionId: String, otherUserId: String, - otherDeviceId: String) - : SASVerificationTransaction( - sasVerificationService, + otherDeviceId: String +) : SASVerificationTransaction( setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, deviceFingerprint, transactionId, otherUserId, @@ -78,14 +66,14 @@ internal class OutgoingSASVerificationRequest( } } - override fun onVerificationStart(startReq: KeyVerificationStart) { - Timber.e("## onVerificationStart - unexpected id:$transactionId") + override fun onVerificationStart(startReq: VerifInfoStart) { + Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId") cancel(CancelCode.UnexpectedMessage) } fun start() { if (state != SasVerificationTxState.None) { - Timber.e("## start verification from invalid state") + Timber.e("## SAS O: start verification from invalid state") // should I cancel?? throw IllegalStateException("Interactive Key verification already started") } @@ -111,10 +99,33 @@ internal class OutgoingSASVerificationRequest( ) } - override fun onVerificationAccept(accept: KeyVerificationAccept) { - Timber.v("## onVerificationAccept id:$transactionId") +// fun request() { +// if (state != SasVerificationTxState.None) { +// Timber.e("## start verification from invalid state") +// // should I cancel?? +// throw IllegalStateException("Interactive Key verification already started") +// } +// +// val requestMessage = KeyVerificationRequest( +// fromDevice = session.sessionParams.credentials.deviceId ?: "", +// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), +// timestamp = System.currentTimeMillis().toInt(), +// transactionID = transactionId +// ) +// +// sendToOther( +// EventType.KEY_VERIFICATION_REQUEST, +// requestMessage, +// SasVerificationTxState.None, +// CancelCode.User, +// null +// ) +// } + + override fun onVerificationAccept(accept: VerifInfoAccept) { + Timber.v("## SAS O: onVerificationAccept id:$transactionId") if (state != SasVerificationTxState.Started) { - Timber.e("## received accept request from invalid state $state") + Timber.e("## SAS O: received accept request from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } @@ -123,7 +134,7 @@ internal class OutgoingSASVerificationRequest( || !KNOWN_HASHES.contains(accept.hash) || !KNOWN_MACS.contains(accept.messageAuthenticationCode) || accept.shortAuthenticationStrings!!.intersect(KNOWN_SHORT_CODES).isEmpty()) { - Timber.e("## received accept request from invalid state") + Timber.e("## SAS O: received accept request from invalid state") cancel(CancelCode.UnknownMethod) return } @@ -137,7 +148,7 @@ internal class OutgoingSASVerificationRequest( // and replies with a to_device message with type set to “m.key.verification.key”, sending Alice’s public key QA val pubKey = getSAS().publicKey - val keyToDevice = KeyVerificationKey.create(transactionId, pubKey) + val keyToDevice = transport.createKey(transactionId, pubKey) // we need to send this to other device now state = SasVerificationTxState.SendingKey sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) { @@ -148,8 +159,8 @@ internal class OutgoingSASVerificationRequest( } } - override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) { - Timber.v("## onKeyVerificationKey id:$transactionId") + override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) { + Timber.v("## SAS O: onKeyVerificationKey id:$transactionId") if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent) { Timber.e("## received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) @@ -163,7 +174,7 @@ internal class OutgoingSASVerificationRequest( // in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message. // check commitment - val concat = vKey.key + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!) + val concat = vKey.key + startReq!!.toCanonicalJson() val otherCommitment = hashUsingAgreedHashMethod(concat) ?: "" if (accepted!!.commitment.equals(otherCommitment)) { @@ -190,14 +201,14 @@ internal class OutgoingSASVerificationRequest( } } - override fun onKeyVerificationMac(vKey: KeyVerificationMac) { - Timber.v("## onKeyVerificationMac id:$transactionId") + override fun onKeyVerificationMac(vKey: VerifInfoMac) { + Timber.v("## SAS O: onKeyVerificationMac id:$transactionId") if (state != SasVerificationTxState.OnKeyReceived && state != SasVerificationTxState.ShortCodeReady && state != SasVerificationTxState.ShortCodeAccepted && state != SasVerificationTxState.SendingMac && state != SasVerificationTxState.MacSent) { - Timber.e("## received key from invalid state $state") + Timber.e("## SAS O: received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index e0cd47e0e0..6552dca7ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -21,6 +21,7 @@ import android.os.Looper import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState @@ -28,6 +29,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction @@ -35,24 +37,23 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask +import im.vector.matrix.android.internal.crypto.tasks.RequestVerificationDMTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.TaskConstraints import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber -import java.lang.Exception -import java.util.UUID +import java.util.* import javax.inject.Inject +import kotlin.collections.ArrayList import kotlin.collections.HashMap - -/** - * Manages all current verifications transactions with short codes. - * Short codes interactive verification is a more user friendly way of verifying devices - * that is still maintaining a good level of security (alternative to the 43-character strings compare method). - */ +import kotlin.collections.set @SessionScope internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials, @@ -61,12 +62,18 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre private val deviceListManager: DeviceListManager, private val setDeviceVerificationAction: SetDeviceVerificationAction, private val sendToDeviceTask: SendToDeviceTask, + private val requestVerificationDMTask: DefaultRequestVerificationDMTask, private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory, + private val sasToDeviceTransportFactory: SasToDeviceTransportFactory, private val taskExecutor: TaskExecutor) : VerificationTransaction.Listener, SasVerificationService { private val uiHandler = Handler(Looper.getMainLooper()) + // Cannot be injected in constructor as it creates a dependency cycle + lateinit var cryptoService: CryptoService + // map [sender : [transaction]] private val txMap = HashMap>() @@ -96,6 +103,39 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } + fun onRoomEvent(event: Event) { + GlobalScope.launch(coroutineDispatchers.crypto) { + when (event.getClearType()) { + EventType.KEY_VERIFICATION_START -> { + onRoomStartRequestReceived(event) + } + EventType.KEY_VERIFICATION_CANCEL -> { + onRoomCancelReceived(event) + } + EventType.KEY_VERIFICATION_ACCEPT -> { + onRoomAcceptReceived(event) + } + EventType.KEY_VERIFICATION_KEY -> { + onRoomKeyRequestReceived(event) + } + EventType.KEY_VERIFICATION_MAC -> { + onRoomMacReceived(event) + } + EventType.KEY_VERIFICATION_DONE -> { + // TODO? + } + EventType.MESSAGE -> { + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { + onRoomRequestReceived(event) + } + } + else -> { + // ignore + } + } + } + } + private var listeners = ArrayList() override fun addListener(listener: SasVerificationService.SasVerificationListener) { @@ -150,15 +190,64 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } + fun onRoomRequestReceived(event: Event) { + // TODO + Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") + } + + private suspend fun onRoomStartRequestReceived(event: Event) { + val startReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + + val otherUserId = event.senderId + if (startReq?.isValid()?.not() == true) { + Timber.e("## received invalid verification request") + if (startReq.transactionID != null) { + sasTransportRoomMessageFactory.createTransport(event.roomId + ?: "", cryptoService).cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + CancelCode.UnknownMethod + ) + } + return + } + + handleStart(otherUserId, startReq as VerifInfoStart) { + it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId + ?: "", cryptoService) + }?.let { + sasTransportRoomMessageFactory.createTransport(event.roomId + ?: "", cryptoService).cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + it + ) + } + } + private suspend fun onStartRequestReceived(event: Event) { + Timber.e("## SAS received Start request ${event.eventId}") val startReq = event.getClearContent().toModel()!! + Timber.v("## SAS received Start request $startReq") val otherUserId = event.senderId if (!startReq.isValid()) { - Timber.e("## received invalid verification request") + Timber.e("## SAS received invalid verification request") if (startReq.transactionID != null) { - cancelTransaction( - startReq.transactionID!!, +// cancelTransaction( +// startReq.transactionID!!, +// otherUserId!!, +// startReq.fromDevice ?: event.getSenderKey()!!, +// CancelCode.UnknownMethod +// ) + sasToDeviceTransportFactory.createTransport(null).cancelTransaction( + startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, CancelCode.UnknownMethod @@ -167,8 +256,22 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre return } // Download device keys prior to everything + handleStart(otherUserId, startReq) { + it.transport = sasToDeviceTransportFactory.createTransport(it) + }?.let { + sasToDeviceTransportFactory.createTransport(null).cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + it + ) + } + } + + private suspend fun handleStart(otherUserId: String?, startReq: VerifInfoStart, txConfigure: (SASVerificationTransaction) -> Unit): CancelCode? { + Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}") if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) { - Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}") + Timber.v("## SAS onStartRequestReceived $startReq") val tid = startReq.transactionID!! val existing = getExistingTransaction(otherUserId, tid) val existingTxs = getExistingTransactionsForUser(otherUserId) @@ -176,43 +279,46 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre // should cancel both! Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}") existing.cancel(CancelCode.UnexpectedMessage) - cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) + return CancelCode.UnexpectedMessage + // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } else if (existingTxs?.isEmpty() == false) { Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}") // Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time. existingTxs.forEach { it.cancel(CancelCode.UnexpectedMessage) } - cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) + return CancelCode.UnexpectedMessage + // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } else { // Ok we can create if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) { Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") - val tx = IncomingSASVerificationTransaction( - this, + val tx = DefaultIncomingSASVerificationTransaction( +// this, setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, myDeviceInfoHolder.get().myDevice.fingerprint()!!, startReq.transactionID!!, - otherUserId) + otherUserId).also { txConfigure(it) } addTransaction(tx) - tx.acceptToDeviceEvent(otherUserId, startReq) + tx.acceptVerificationEvent(otherUserId, startReq) } else { Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") - cancelTransaction(tid, otherUserId, startReq.fromDevice - ?: event.getSenderKey()!!, CancelCode.UnknownMethod) + return CancelCode.UnknownMethod + // cancelTransaction(tid, otherUserId, startReq.fromDevice +// ?: event.getSenderKey()!!, CancelCode.UnknownMethod) } } } else { - cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) + return CancelCode.UnexpectedMessage +// cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } + return null } private suspend fun checkKeysAreDownloaded(otherUserId: String, - startReq: KeyVerificationStart): MXUsersDevicesMap? { + startReq: VerifInfoStart): MXUsersDevicesMap? { return try { val keys = deviceListManager.downloadKeys(listOf(otherUserId), true) val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null @@ -222,17 +328,36 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } - private suspend fun onCancelReceived(event: Event) { + private fun onRoomCancelReceived(event: Event) { + val cancelReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (cancelReq == null || cancelReq.isValid().not()) { + // ignore + Timber.e("## SAS Received invalid key request") + // TODO should we cancel? + return + } + handleOnCancel(event.senderId!!, cancelReq) + } + + private fun onCancelReceived(event: Event) { Timber.v("## SAS onCancelReceived") val cancelReq = event.getClearContent().toModel()!! if (!cancelReq.isValid()) { // ignore - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } val otherUserId = event.senderId!! + handleOnCancel(otherUserId, cancelReq) + } + + private fun handleOnCancel(otherUserId: String, cancelReq: VerifInfoCancel) { Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}") val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!) if (existing == null) { @@ -245,65 +370,119 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } - private suspend fun onAcceptReceived(event: Event) { - val acceptReq = event.getClearContent().toModel()!! + private fun onRoomAcceptReceived(event: Event) { + Timber.d("## SAS Received Accept via DM $event") + val accept = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + ?: return + handleAccept(accept, event.senderId!!) + } + private fun onAcceptReceived(event: Event) { + Timber.d("## SAS Received Accept $event") + val acceptReq = event.getClearContent().toModel() ?: return + handleAccept(acceptReq, event.senderId!!) + } + + private fun handleAccept(acceptReq: VerifInfoAccept, senderId: String) { if (!acceptReq.isValid()) { // ignore - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } - val otherUserId = event.senderId!! + val otherUserId = senderId val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!) if (existing == null) { - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } if (existing is SASVerificationTransaction) { - existing.acceptToDeviceEvent(otherUserId, acceptReq) + existing.acceptVerificationEvent(otherUserId, acceptReq) } else { // not other types now } } - private suspend fun onKeyReceived(event: Event) { + private fun onRoomKeyRequestReceived(event: Event) { + val keyReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (keyReq == null || keyReq.isValid().not()) { + // ignore + Timber.e("## SAS Received invalid key request") + // TODO should we cancel? + return + } + handleKeyReceived(event, keyReq) + } + + private fun onKeyReceived(event: Event) { val keyReq = event.getClearContent().toModel()!! if (!keyReq.isValid()) { // ignore - Timber.e("## Received invalid key request") + Timber.e("## SAS Received invalid key request") return } + handleKeyReceived(event, keyReq) + } + + private fun handleKeyReceived(event: Event, keyReq: VerifInfoKey) { + Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq") val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!) if (existing == null) { - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } if (existing is SASVerificationTransaction) { - existing.acceptToDeviceEvent(otherUserId, keyReq) + existing.acceptVerificationEvent(otherUserId, keyReq) } else { // not other types now } } - private suspend fun onMacReceived(event: Event) { - val macReq = event.getClearContent().toModel()!! - - if (!macReq.isValid()) { + private fun onRoomMacReceived(event: Event) { + val macReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (macReq == null || macReq.isValid().not() || event.senderId == null) { // ignore - Timber.e("## Received invalid key request") + Timber.e("## SAS Received invalid mac request") + // TODO should we cancel? return } - val otherUserId = event.senderId!! - val existing = getExistingTransaction(otherUserId, macReq.transactionID!!) + handleMacReceived(event.senderId, macReq) + } + + private fun onMacReceived(event: Event) { + val macReq = event.getClearContent().toModel()!! + + if (!macReq.isValid() || event.senderId == null) { + // ignore + Timber.e("## SAS Received invalid mac request") + return + } + handleMacReceived(event.senderId, macReq) + } + + private fun handleMacReceived(senderId: String, macReq: VerifInfoMac) { + Timber.v("## SAS Received $macReq") + val existing = getExistingTransaction(senderId, macReq.transactionID!!) if (existing == null) { - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } if (existing is SASVerificationTransaction) { - existing.acceptToDeviceEvent(otherUserId, macReq) + existing.acceptVerificationEvent(senderId, macReq) } else { // not other types known for now } @@ -346,13 +525,10 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre val txID = createUniqueIDForTransaction(userId, deviceID) // should check if already one (and cancel it) if (KeyVerificationStart.VERIF_METHOD_SAS == method) { - val tx = OutgoingSASVerificationRequest( - this, + val tx = DefaultOutgoingSASVerificationRequest( setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, myDeviceInfoHolder.get().myDevice.fingerprint()!!, txID, userId, @@ -366,6 +542,30 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } + override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) { + requestVerificationDMTask.configureWith( + RequestVerificationDMTask.Params( + roomId = roomId, + from = credentials.deviceId ?: "", + methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + to = userId, + cryptoService = cryptoService + ) + ) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: SendResponse) { + callback?.onSuccess(data.eventId) + } + + override fun onFailure(failure: Throwable) { + callback?.onFailure(failure) + } + } + constraints = TaskConstraints(true) + retryCount = 3 + }.executeBy(taskExecutor) + } + /** * This string must be unique for the pair of users performing verification for the duration that the transaction is valid */ @@ -390,24 +590,28 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre this.removeTransaction(tx.otherUserId, tx.transactionId) } } - - fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { - val cancelMessage = KeyVerificationCancel.create(transactionId, code) - val contentMap = MXUsersDevicesMap() - contentMap.setObject(userId, userDevice, cancelMessage) - - sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") - } - } - } - .executeBy(taskExecutor) - } +// +// fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode, roomId: String? = null) { +// val cancelMessage = KeyVerificationCancel.create(transactionId, code) +// val contentMap = MXUsersDevicesMap() +// contentMap.setObject(userId, userDevice, cancelMessage) +// +// if (roomId != null) { +// +// } else { +// sendToDeviceTask +// .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: Unit) { +// Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") +// } +// +// override fun onFailure(failure: Throwable) { +// Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") +// } +// } +// } +// .executeBy(taskExecutor) +// } +// } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 589103d38a..443443afad 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.crypto.verification import android.os.Build -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation @@ -26,13 +25,8 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXKey -import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.extensions.toUnsignedInt -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith import org.matrix.olm.OlmSAS import org.matrix.olm.OlmUtility import timber.log.Timber @@ -42,12 +36,9 @@ import kotlin.properties.Delegates * Represents an ongoing short code interactive key verification between two devices. */ internal abstract class SASVerificationTransaction( - private val sasVerificationService: DefaultSasVerificationService, private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val credentials: Credentials, + open val credentials: Credentials, private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor, private val deviceFingerprint: String, transactionId: String, otherUserId: String, @@ -55,6 +46,8 @@ internal abstract class SASVerificationTransaction( isIncoming: Boolean) : VerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming) { + lateinit var transport: SasTransport + companion object { const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256" const val SAS_MAC_SHA256 = "hkdf-hmac-sha256" @@ -95,13 +88,13 @@ internal abstract class SASVerificationTransaction( private var olmSas: OlmSAS? = null - var startReq: KeyVerificationStart? = null - var accepted: KeyVerificationAccept? = null + var startReq: VerifInfoStart? = null + var accepted: VerifInfoAccept? = null var otherKey: String? = null var shortCodeBytes: ByteArray? = null - var myMac: KeyVerificationMac? = null - var theirMac: KeyVerificationMac? = null + var myMac: VerifInfoMac? = null + var theirMac: VerifInfoMac? = null fun getSAS(): OlmSAS { if (olmSas == null) olmSas = OlmSAS() @@ -160,7 +153,7 @@ internal abstract class SASVerificationTransaction( return } - val macMsg = KeyVerificationMac.create(transactionId, mapOf(keyId to macString), keyStrings) + val macMsg = transport.createMac(transactionId, mapOf(keyId to macString), keyStrings) myMac = macMsg state = SasVerificationTxState.SendingMac sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, SasVerificationTxState.MacSent, CancelCode.User) { @@ -176,25 +169,25 @@ internal abstract class SASVerificationTransaction( } // if not wait for it } - override fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject) { - when (event) { - is KeyVerificationStart -> onVerificationStart(event) - is KeyVerificationAccept -> onVerificationAccept(event) - is KeyVerificationKey -> onKeyVerificationKey(senderId, event) - is KeyVerificationMac -> onKeyVerificationMac(event) - else -> { + override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) { + when (info) { + is VerifInfoStart -> onVerificationStart(info) + is VerifInfoAccept -> onVerificationAccept(info) + is VerifInfoKey -> onKeyVerificationKey(senderId, info) + is VerifInfoMac -> onKeyVerificationMac(info) + else -> { // nop } } } - abstract fun onVerificationStart(startReq: KeyVerificationStart) + abstract fun onVerificationStart(startReq: VerifInfoStart) - abstract fun onVerificationAccept(accept: KeyVerificationAccept) + abstract fun onVerificationAccept(accept: VerifInfoAccept) - abstract fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) + abstract fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) - abstract fun onKeyVerificationMac(vKey: KeyVerificationMac) + abstract fun onKeyVerificationMac(vKey: VerifInfoMac) protected fun verifyMacs() { Timber.v("## SAS verifying macs for id:$transactionId") @@ -245,7 +238,7 @@ internal abstract class SASVerificationTransaction( // if none of the keys could be verified, then error because the app // should be informed about that if (verifiedDevices.isEmpty()) { - Timber.e("Verification: No devices verified") + Timber.e("## SAS Verification: No devices verified") cancel(CancelCode.MismatchedKeys) return } @@ -254,6 +247,7 @@ internal abstract class SASVerificationTransaction( verifiedDevices.forEach { setDeviceVerified(it, otherUserId) } + transport.done(transactionId) state = SasVerificationTxState.Verified } @@ -270,41 +264,15 @@ internal abstract class SASVerificationTransaction( override fun cancel(code: CancelCode) { cancelledReason = code state = SasVerificationTxState.Cancelled - sasVerificationService.cancelTransaction( - transactionId, - otherUserId, - otherDeviceId ?: "", - code) + transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code) } protected fun sendToOther(type: String, - keyToDevice: Any, + keyToDevice: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { - val contentMap = MXUsersDevicesMap() - contentMap.setObject(otherUserId, otherDeviceId, keyToDevice) - - sendToDeviceTask - .configureWith(SendToDeviceTask.Params(type, contentMap, transactionId)) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.") - if (onDone != null) { - onDone() - } else { - state = nextState - } - } - - override fun onFailure(failure: Throwable) { - Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state") - - cancel(onErrorReason) - } - } - } - .executeBy(taskExecutor) + transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone) } fun getShortCodeRepresentation(shortAuthenticationStringMode: String): String? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt new file mode 100644 index 0000000000..23ebd89f7a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState + +/** + * SAS verification can be performed using toDevice events or via DM. + * This class abstracts the concept of transport for SAS + */ +internal interface SasTransport { + + /** + * Sends a message + */ + fun sendToOther(type: String, + verificationInfo: VerificationInfo, + nextState: SasVerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)?) + + fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) + + fun done(transactionId: String) + /** + * Creates an accept message suitable for this transport + */ + fun createAccept(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept + + fun createKey(tid: String, + pubKey: String): VerifInfoKey + + fun createMac(tid: String, mac: Map, keys: String): VerifInfoMac +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt new file mode 100644 index 0000000000..1173914ba1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask +import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.TaskConstraints +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber +import javax.inject.Inject + +internal class SasTransportRoomMessage constructor( + private val roomId: String, + private val cryptoService: CryptoService, +// private val tx: SASVerificationTransaction?, + private val sendVerificationMessageTask: SendVerificationMessageTask, + private val taskExecutor: TaskExecutor +) : SasTransport { + + override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { + Timber.d("## SAS sending msg type $type") + Timber.v("## SAS sending msg info $verificationInfo") + sendVerificationMessageTask.configureWith( + SendVerificationMessageTask.Params( + type, + roomId, + verificationInfo.toEventContent()!!, + cryptoService + ) + ) { + constraints = TaskConstraints(true) + retryCount = 3 + } + .executeBy(taskExecutor) + } + + override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { + Timber.d("## SAS canceling transaction $transactionId for reason $code") + sendVerificationMessageTask.configureWith( + SendVerificationMessageTask.Params( + EventType.KEY_VERIFICATION_CANCEL, + roomId, + MessageVerificationCancelContent.create(transactionId, code).toContent(), + cryptoService + ) + ) { + constraints = TaskConstraints(true) + retryCount = 3 + callback = object : MatrixCallback { + override fun onSuccess(data: SendResponse) { + Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") + } + } + } + .executeBy(taskExecutor) + } + + override fun done(transactionId: String) { + sendVerificationMessageTask.configureWith( + SendVerificationMessageTask.Params( + EventType.KEY_VERIFICATION_DONE, + roomId, + MessageVerificationDoneContent( + relatesTo = RelationDefaultContent( + RelationType.REFERENCE, + transactionId + ) + ).toContent(), + cryptoService + ) + ) { + constraints = TaskConstraints(true) + retryCount = 3 + } + .executeBy(taskExecutor) + } + + override fun createAccept(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List) + : VerifInfoAccept = MessageVerificationAcceptContent.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + + override fun createKey(tid: String, pubKey: String): VerifInfoKey = MessageVerificationKeyContent.create(tid, pubKey) + + override fun createMac(tid: String, mac: Map, keys: String) = MessageVerificationMacContent.create(tid, mac, keys) +} + +internal class SasTransportRoomMessageFactory @Inject constructor( + private val sendVerificationMessageTask: DefaultSendVerificationMessageTask, + private val taskExecutor: TaskExecutor) { + + fun createTransport(roomId: String, + cryptoService: CryptoService +// tx: SASVerificationTransaction? + ): SasTransportRoomMessage { + return SasTransportRoomMessage(roomId, cryptoService, /*tx,*/ sendVerificationMessageTask, taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt new file mode 100644 index 0000000000..4f36a5218e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber +import javax.inject.Inject + +internal class SasTransportToDevice( + private var tx: SASVerificationTransaction?, + private var sendToDeviceTask: SendToDeviceTask, + private var taskExecutor: TaskExecutor +) : SasTransport { + + override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { + Timber.d("## SAS sending msg type $type") + Timber.v("## SAS sending msg info $verificationInfo") + val tx = tx ?: return + val contentMap = MXUsersDevicesMap() + val toSendToDeviceObject = verificationInfo.toSendToDeviceObject() + ?: return Unit.also { tx.cancel() } + + contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject) + + sendToDeviceTask + .configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.") + if (onDone != null) { + onDone() + } else { + tx.state = nextState + } + } + + override fun onFailure(failure: Throwable) { + Timber.e("## SAS verification [$tx.transactionId] failed to send toDevice in state : $tx.state") + + tx.cancel(onErrorReason) + } + } + } + .executeBy(taskExecutor) + } + + override fun done(transactionId: String) { + // To device do not do anything here + } + + override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { + Timber.d("## SAS canceling transaction $transactionId for reason $code") + val cancelMessage = KeyVerificationCancel.create(transactionId, code) + val contentMap = MXUsersDevicesMap() + contentMap.setObject(userId, userDevice, cancelMessage) + sendToDeviceTask + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") + } + } + } + .executeBy(taskExecutor) + } + + override fun createAccept(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List) + : VerifInfoAccept = KeyVerificationAccept.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + + override fun createKey(tid: String, pubKey: String): VerifInfoKey = KeyVerificationKey.create(tid, pubKey) + + override fun createMac(tid: String, mac: Map, keys: String) = KeyVerificationMac.create(tid, mac, keys) +} + +internal class SasToDeviceTransportFactory @Inject constructor( + private val sendToDeviceTask: SendToDeviceTask, + private val taskExecutor: TaskExecutor) { + + fun createTransport(tx: SASVerificationTransaction?): SasTransportToDevice { + return SasTransportToDevice(tx, sendToDeviceTask, taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt new file mode 100644 index 0000000000..c0677439f4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +internal interface VerifInfoAccept : VerificationInfo { + + val transactionID: String? + + /** + * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device + */ + val keyAgreementProtocol: String? + + /** + * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device + */ + val hash: String? + + /** + * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device + */ + val messageAuthenticationCode: String? + + /** + * An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device + */ + val shortAuthenticationStrings: List? + + /** + * The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64) + * and the canonical JSON representation of the m.key.verification.start message. + */ + var commitment: String? +} + +internal interface AcceptVerifInfoFactory { + + fun create(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt new file mode 100644 index 0000000000..94c52f61ea --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +interface VerifInfoCancel : VerificationInfo { + + val transactionID: String? + /** + * machine-readable reason for cancelling, see #CancelCode + */ + val code: String? + + /** + * human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given. + */ + val reason: String? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt new file mode 100644 index 0000000000..69ae917938 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +/** + * Sent by both devices to send their ephemeral Curve25519 public key to the other device. + */ +internal interface VerifInfoKey : VerificationInfo { + + val transactionID: String? + /** + * The device’s ephemeral public key, as an unpadded base64 string + */ + val key: String? +} + +internal interface KeyVerifInfoFactory { + fun create(tid: String, pubKey: String): VerifInfoKey +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt new file mode 100644 index 0000000000..14da21a398 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +internal interface VerifInfoMac : VerificationInfo { + + val transactionID: String? + + /** + * A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key + */ + val mac: Map? + + /** + * The MAC of the comma-separated, sorted list of key IDs given in the mac property, + * as an unpadded base64 string, calculated using the MAC key. + * For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will + * give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”. + */ + val keys: String? +} + +internal interface VerifInfoMacFactory { + fun create(tid: String, mac: Map, keys: String) : VerifInfoMac +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt new file mode 100644 index 0000000000..380022def7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +interface VerifInfoStart : VerificationInfo { + + val method: String? + val fromDevice: String? + + val transactionID: String? + + val keyAgreementProtocols: List? + + /** + * An array of hashes that Alice’s client understands. + * Must include “sha256”. Other methods may be defined in the future. + */ + val hashes: List? + + /** + * An array of message authentication codes that Alice’s client understands. + * Must include “hkdf-hmac-sha256”. + * Other methods may be defined in the future. + */ + val messageAuthenticationCodes: List? + + /** + * An array of short authentication string methods that Alice’s client (and Alice) understands. + * Must include “decimal”. + * This document also describes the “emoji” method. + * Other methods may be defined in the future + */ + val shortAuthenticationStrings: List? + + fun toCanonicalJson(): String? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt new file mode 100644 index 0000000000..5fe5c62edd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject + +interface VerificationInfo { + fun toEventContent(): Content? = null + fun toSendToDeviceObject(): SendToDeviceObject? = null + fun isValid() : Boolean +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt new file mode 100644 index 0000000000..2886d78d8c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto.verification + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.database.RealmLiveEntityObserver +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.types +import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.task.TaskExecutor +import io.realm.OrderedCollectionChangeSet +import io.realm.RealmConfiguration +import io.realm.RealmResults +import timber.log.Timber +import java.util.* +import javax.inject.Inject + +internal class VerificationMessageLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, + @UserId private val userId: String, + private val cryptoService: CryptoService, + private val sasVerificationService: DefaultSasVerificationService, + private val taskExecutor: TaskExecutor) : + RealmLiveEntityObserver(realmConfiguration) { + + override val query = Monarchy.Query { + EventEntity.types(it, listOf( + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE, + EventType.MESSAGE, + EventType.ENCRYPTED) + ) + } + + override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { + // TODO do that in a task + // TODO how to ignore when it's an initial sync? + val events = changeSet.insertions + .asSequence() + .mapNotNull { results[it]?.asDomain() } + .filterNot { + // ignore mines ^^ + it.senderId == userId + } + .toList() + + events.forEach { event -> + Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") + Timber.v("## SAS Verification live observer: received msgId: $event") + + // decrypt if needed? + + if (event.isEncrypted() && event.mxDecryptionResult == null) { + // TODO use a global event decryptor? attache to session and that listen to new sessionId? + // for now decrypt sync + try { + val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString()) + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.e("## SAS Failed to decrypt event: ${event.eventId}") + } + } + Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") + when (event.getClearType()) { + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE -> { + sasVerificationService.onRoomEvent(event) + } + EventType.MESSAGE -> { + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { + // TODO If the request is in the future by more than 5 minutes or more than 10 minutes in the past, + // the message should be ignored by the receiver. + sasVerificationService.onRoomRequestReceived(event) + } + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt index be3f4c7885..d6cc5e3279 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction -import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject /** * Generic interactive key verification transaction @@ -42,7 +41,7 @@ internal abstract class VerificationTransaction( listeners.remove(listener) } - abstract fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject) + abstract fun acceptVerificationEvent(senderId: String, info: VerificationInfo) abstract fun cancel(code: CancelCode) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index fa0b9a1f1c..a89e21b04a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -104,7 +104,7 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { return Failure.ServerError(matrixError, httpCode) } - } catch (ex: JsonDataException) { + } catch (ex: Exception) { // This is not a MatrixError Timber.w("The error returned by the server is not a MatrixError") } catch (ex: JsonEncodingException) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 0e88894969..b7d121998c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService +import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.* @@ -164,6 +165,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver + @Binds + @IntoSet + abstract fun bindVerificationEventObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver + @Binds abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 8fad03b588..acbe385ff6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -157,7 +157,8 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private override fun deleteFailedEcho(localEcho: TimelineEvent) { monarchy.writeAsync { realm -> - TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId + ?: "").findFirst()?.let { it.deleteFromRealm() } EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 0fed1ca6f5..a225146d83 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -286,6 +286,24 @@ internal class LocalEchoEventFactory @Inject constructor( ) } + fun createVerificationRequest(roomId: String, fromDevice: String, to: String, methods: List): Event { + val localID = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localID, + type = EventType.MESSAGE, + content = MessageVerificationRequestContent( + body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId), + fromDevice = fromDevice, + to = to, + methods = methods + ).toContent(), + unsignedData = UnsignedData(age = null, transactionId = localID) + ) + } + private fun dummyOriginServerTs(): Long { return System.currentTimeMillis() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt index 4c45ba0a4d..d6d924ab46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt @@ -20,7 +20,6 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject @@ -28,7 +27,7 @@ internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarc suspend fun updateSendState(eventId: String, sendState: SendState) { Timber.v("Update local state of $eventId to ${sendState.name}") - monarchy.awaitTransaction { realm -> + monarchy.writeAsync { realm -> val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() if (sendingEventEntity != null) { if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index a22533c6d1..4fe6009268 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -17,4 +17,7 @@ %1$s withdrew %2$s\'s invitation. Reason: %3$s There is no network connection right now + + %s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys. + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 442c5f6f96..94d7a39379 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -207,6 +207,11 @@ interface FragmentModule { @FragmentKey(VectorSettingsNotificationPreferenceFragment::class) fun bindVectorSettingsNotificationPreferenceFragment(fragment: VectorSettingsNotificationPreferenceFragment): Fragment + @Binds + @IntoMap + @FragmentKey(VectorSettingsLabsFragment::class) + fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment + @Binds @IntoMap @FragmentKey(VectorSettingsPreferencesFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 8b72ffa4a6..fbc3ddebe5 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -38,7 +38,9 @@ enum class Command(val command: String, val parameters: String, @StringRes val d CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), MARKDOWN("/markdown", "", R.string.command_description_markdown), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), - SPOILER("/spoiler", "", R.string.command_description_spoiler); + SHRUG("/shrug", "", R.string.command_description_shrug), + // TODO temporary command + VERIFY_USER("/verify", "", R.string.command_description_spoiler); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index 359f2c1f13..23e30e1d3c 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -244,6 +244,17 @@ object CommandParser { ParsedCommand.SendSpoiler(message) } + Command.SHRUG.command -> { + val message = textMessage.subSequence(Command.SHRUG.command.length, textMessage.length).trim() + + ParsedCommand.SendShrug(message) + } + + Command.VERIFY_USER.command -> { + val message = textMessage.substring(Command.VERIFY_USER.command.length).trim() + + ParsedCommand.VerifyUser(message) + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index dd7c0c7e86..b16f68c7b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -46,4 +46,6 @@ sealed class ParsedCommand { class SetMarkdown(val enable: Boolean) : ParsedCommand() object ClearScalarToken : ParsedCommand() class SendSpoiler(val message: String) : ParsedCommand() + class SendShrug(val message: CharSequence) : ParsedCommand() + class VerifyUser(val userId: String) : ParsedCommand() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index e7a18753cd..efdfd53234 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -50,7 +50,6 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap -import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel @@ -377,6 +376,25 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) popDraft() } + is ParsedCommand.SendShrug -> { + val sequence: CharSequence = buildString { + append("¯\\_(ツ)_/¯") + .apply { + if (slashCommandResult.message.isNotEmpty()) { + append(" ") + append(slashCommandResult.message) + } + } + } + room.sendTextMessage(sequence) + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } + is ParsedCommand.VerifyUser -> { + session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId, null) + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) popDraft() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 5b6dec9900..4c36b55fef 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -64,6 +64,16 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me encryptedItemFactory.create(event, nextEvent, highlight, callback) } } + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC -> { + // These events are filtered from timeline in normal case + // Only visible in developer mode + defaultItemFactory.create(event, highlight, readMarkerVisible, callback) + } // Unhandled event types (yet) EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 1cd851f8c8..033ff68433 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -42,7 +42,13 @@ object TimelineDisplayableEvents { val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf( EventType.REDACTION, - EventType.REACTION + EventType.REACTION, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_KEY ) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index dd99488465..ee8c0530d9 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -23,6 +23,7 @@ import android.net.Uri import android.provider.MediaStore import androidx.core.content.edit import androidx.preference.PreferenceManager +import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.features.homeserver.ServerUrlsRepository import im.vector.riotx.features.themes.ThemeUtils @@ -256,7 +257,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun labAllowedExtendedLogging(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false) + return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, BuildConfig.DEBUG) } /** diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt index 37dfd02c43..cd201900fb 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt @@ -17,14 +17,21 @@ package im.vector.riotx.features.settings import im.vector.riotx.R +import im.vector.riotx.core.preference.VectorSwitchPreference +import javax.inject.Inject -class VectorSettingsLabsFragment : VectorSettingsBaseFragment() { +class VectorSettingsLabsFragment @Inject constructor(val vectorPreferences: VectorPreferences) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title override val preferenceXmlRes = R.xml.vector_settings_labs override fun bindPref() { // Lab + + findPreference(VectorPreferences.SETTINGS_LABS_ALLOW_EXTENDED_LOGS)?.let { + it.isChecked = vectorPreferences.labAllowedExtendedLogging() + } + // val useCryptoPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference // val cryptoIsEnabledPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 2e4d04354b..10ca8e728e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1695,6 +1695,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Name or ID (#example:matrix.org) Enable swipe to reply in timeline + Enable verification other DM Link copied to clipboard diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index f259a34e44..f8d65fab15 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -12,6 +12,8 @@ "Leave the room" "%1$s made no changes" Sends the given message as a spoiler + Request to verify the given userID + Prepends ¯\\_(ツ)_/¯ to a plain-text message Spoiler Type keywords to find a reaction. diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index e9e5e27198..1a64f75de5 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -45,7 +45,6 @@ android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" android:title="@string/labs_swipe_to_reply_in_timeline" /> -