mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 21:48:50 +03:00
Support verification using room transport
This commit is contained in:
parent
79ef055bfb
commit
26b4b6e194
57 changed files with 1939 additions and 288 deletions
|
@ -33,6 +33,7 @@ Changes in RiotX 0.9.0 (2019-12-05)
|
||||||
Features ✨:
|
Features ✨:
|
||||||
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
||||||
- Iteration of the login flow (#613)
|
- Iteration of the login flow (#613)
|
||||||
|
- [SDK] MSC2241 / verification in DMs (#707)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Send mention Pills from composer
|
- Send mention Pills from composer
|
||||||
|
|
|
@ -16,19 +16,42 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
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 {
|
interface SasVerificationService {
|
||||||
|
|
||||||
fun addListener(listener: SasVerificationListener)
|
fun addListener(listener: SasVerificationListener)
|
||||||
|
|
||||||
fun removeListener(listener: SasVerificationListener)
|
fun removeListener(listener: SasVerificationListener)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this device as verified manually
|
||||||
|
*/
|
||||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||||
|
|
||||||
fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction?
|
fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for KeyVerificationStart.VERIF_METHOD_SAS
|
||||||
|
* @see beginKeyVerification
|
||||||
|
*/
|
||||||
fun beginKeyVerificationSAS(userId: String, deviceID: String): String?
|
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 beginKeyVerification(method: String, userId: String, deviceID: String): String?
|
||||||
|
|
||||||
|
fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?)
|
||||||
|
|
||||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||||
|
|
||||||
interface SasVerificationListener {
|
interface SasVerificationListener {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
package im.vector.matrix.android.api.session.crypto.sas
|
||||||
|
|
||||||
interface SasVerificationTransaction {
|
interface SasVerificationTransaction {
|
||||||
val state: SasVerificationTxState
|
var state: SasVerificationTxState
|
||||||
|
|
||||||
val cancelledReason: CancelCode?
|
val cancelledReason: CancelCode?
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ object EventType {
|
||||||
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
||||||
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
||||||
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
||||||
|
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
|
||||||
|
|
||||||
// Relation Events
|
// Relation Events
|
||||||
const val REACTION = "m.reaction"
|
const val REACTION = "m.reaction"
|
||||||
|
|
|
@ -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?
|
||||||
|
)
|
|
@ -26,6 +26,7 @@ object MessageType {
|
||||||
const val MSGTYPE_VIDEO = "m.video"
|
const val MSGTYPE_VIDEO = "m.video"
|
||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
|
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||||
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
|
|
|
@ -0,0 +1,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<String>?,
|
||||||
|
@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<String>): VerifInfoAccept {
|
||||||
|
return MessageVerificationAcceptContent(
|
||||||
|
hash,
|
||||||
|
keyAgreementProtocol,
|
||||||
|
messageAuthenticationCode,
|
||||||
|
shortAuthenticationStrings,
|
||||||
|
RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
),
|
||||||
|
commitment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, String>? = 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<String, String>, keys: String): VerifInfoMac {
|
||||||
|
return MessageVerificationMacContent(
|
||||||
|
mac,
|
||||||
|
keys,
|
||||||
|
RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String>,
|
||||||
|
@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
|
|
@ -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<String>?,
|
||||||
|
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>?,
|
||||||
|
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>?,
|
||||||
|
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>?,
|
||||||
|
@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()
|
||||||
|
}
|
|
@ -177,6 +177,9 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
||||||
: ClaimOneTimeKeysForUsersDeviceTask
|
: ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
|
|
@ -136,6 +136,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val cryptoCoroutineScope: CoroutineScope
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
|
init {
|
||||||
|
sasVerificationService.cryptoService = this
|
||||||
|
}
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
// MXEncrypting instance for each room.
|
// MXEncrypting instance for each room.
|
||||||
|
|
|
@ -17,13 +17,15 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationAccept(
|
internal data class KeyVerificationAccept(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* string to identify the transaction.
|
* 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.
|
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@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
|
* 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")
|
@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
|
* 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
|
* 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")
|
@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
|
* 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")
|
@Json(name = "short_authentication_string")
|
||||||
var shortAuthenticationStrings: List<String>? = null,
|
override var shortAuthenticationStrings: List<String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64)
|
* 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.
|
* and the canonical JSON representation of the m.key.verification.start message.
|
||||||
*/
|
*/
|
||||||
var commitment: String? = null
|
@Json(name = "commitment")
|
||||||
) : SendToDeviceObject {
|
override var commitment: String? = null
|
||||||
|
) : SendToDeviceObject, VerifInfoAccept {
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank()
|
if (transactionID.isNullOrBlank()
|
||||||
|| keyAgreementProtocol.isNullOrBlank()
|
|| keyAgreementProtocol.isNullOrBlank()
|
||||||
|| hash.isNullOrBlank()
|
|| hash.isNullOrBlank()
|
||||||
|
@ -76,13 +80,15 @@ data class KeyVerificationAccept(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun toSendToDeviceObject() = this
|
||||||
fun create(tid: String,
|
|
||||||
|
companion object : AcceptVerifInfoFactory {
|
||||||
|
override fun create(tid: String,
|
||||||
keyAgreementProtocol: String,
|
keyAgreementProtocol: String,
|
||||||
hash: String,
|
hash: String,
|
||||||
commitment: String,
|
commitment: String,
|
||||||
messageAuthenticationCode: String,
|
messageAuthenticationCode: String,
|
||||||
shortAuthenticationStrings: List<String>): KeyVerificationAccept {
|
shortAuthenticationStrings: List<String>): VerifInfoAccept {
|
||||||
return KeyVerificationAccept().apply {
|
return KeyVerificationAccept().apply {
|
||||||
this.transactionID = tid
|
this.transactionID = tid
|
||||||
this.keyAgreementProtocol = keyAgreementProtocol
|
this.keyAgreementProtocol = keyAgreementProtocol
|
||||||
|
|
|
@ -18,40 +18,43 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
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.
|
* To device event sent by either party to cancel a key verification.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationCancel(
|
internal data class KeyVerificationCancel(
|
||||||
/**
|
/**
|
||||||
* the transaction ID of the verification to cancel
|
* the transaction ID of the verification to cancel
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id")
|
||||||
var transactionID: String? = null,
|
override val transactionID: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* machine-readable reason for cancelling, see #CancelCode
|
* 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.
|
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
||||||
*/
|
*/
|
||||||
var reason: String? = null
|
override var reason: String? = null
|
||||||
) : SendToDeviceObject {
|
) : SendToDeviceObject, VerifInfoCancel {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel {
|
fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel {
|
||||||
return KeyVerificationCancel().apply {
|
return KeyVerificationCancel(
|
||||||
this.transactionID = tid
|
tid,
|
||||||
this.code = cancelCode.value
|
cancelCode.value,
|
||||||
this.reason = cancelCode.humanReadable
|
cancelCode.humanReadable
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,37 +17,33 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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.
|
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationKey(
|
internal data class KeyVerificationKey(
|
||||||
/**
|
/**
|
||||||
* the ID of the transaction that the message is part of
|
* the ID of the transaction that the message is part of
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id") override var transactionID: String? = null,
|
||||||
@JvmField
|
|
||||||
var transactionID: String? = null,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The device’s ephemeral public key, as an unpadded base64 string
|
* The device’s ephemeral public key, as an unpadded base64 string
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@Json(name = "key") override val key: String? = null
|
||||||
var key: String? = null
|
|
||||||
|
|
||||||
) : SendToDeviceObject {
|
) : SendToDeviceObject, VerifInfoKey {
|
||||||
|
|
||||||
companion object {
|
companion object : KeyVerifInfoFactory {
|
||||||
fun create(tid: String, key: String): KeyVerificationKey {
|
override fun create(tid: String, pubKey: String): KeyVerificationKey {
|
||||||
return KeyVerificationKey().apply {
|
return KeyVerificationKey(tid, pubKey)
|
||||||
this.transactionID = tid
|
|
||||||
this.key = key
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,49 +17,32 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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.
|
* Sent by both devices to send the MAC of their device key to the other device.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeyVerificationMac(
|
internal data class KeyVerificationMac(
|
||||||
/**
|
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||||
* the ID of the transaction that the message is part of
|
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
||||||
*/
|
@Json(name = "key") override val keys: String? = null
|
||||||
@Json(name = "transaction_id")
|
|
||||||
var transactionID: String? = null,
|
|
||||||
|
|
||||||
/**
|
) : SendToDeviceObject, VerifInfoMac {
|
||||||
* 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<String, String>? = null,
|
|
||||||
|
|
||||||
/**
|
override fun isValid(): Boolean {
|
||||||
* 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 {
|
|
||||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
||||||
fun create(tid: String, mac: Map<String, String>, keys: String): KeyVerificationMac {
|
|
||||||
return KeyVerificationMac().apply {
|
companion object : VerifInfoMacFactory {
|
||||||
this.transactionID = tid
|
override fun create(tid: String, mac: Map<String, String>, keys: String): VerifInfoMac {
|
||||||
this.mac = mac
|
return KeyVerificationMac(tid, mac, keys)
|
||||||
this.keys = keys
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String> = 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,21 +19,27 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
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.SASVerificationTransaction
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart
|
||||||
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by Alice to initiate an interactive key verification.
|
* Sent by Alice to initiate an interactive key verification.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@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
|
* Alice’s device ID
|
||||||
*/
|
*/
|
||||||
@Json(name = "from_device")
|
@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.
|
* 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.
|
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||||
*/
|
*/
|
||||||
@Json(name = "transaction_id")
|
@Json(name = "transaction_id")
|
||||||
var transactionID: String? = null
|
override var transactionID: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of key agreement protocols that Alice’s client understands.
|
* 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
|
* Other methods may be defined in the future
|
||||||
*/
|
*/
|
||||||
@Json(name = "key_agreement_protocols")
|
@Json(name = "key_agreement_protocols")
|
||||||
var keyAgreementProtocols: List<String>? = null
|
override var keyAgreementProtocols: List<String>? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of hashes that Alice’s client understands.
|
* An array of hashes that Alice’s client understands.
|
||||||
* Must include “sha256”. Other methods may be defined in the future.
|
* Must include “sha256”. Other methods may be defined in the future.
|
||||||
*/
|
*/
|
||||||
var hashes: List<String>? = null
|
override var hashes: List<String>? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of message authentication codes that Alice’s client understands.
|
* 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.
|
* Other methods may be defined in the future.
|
||||||
*/
|
*/
|
||||||
@Json(name = "message_authentication_codes")
|
@Json(name = "message_authentication_codes")
|
||||||
var messageAuthenticationCodes: List<String>? = null
|
override var messageAuthenticationCodes: List<String>? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of short authentication string methods that Alice’s client (and Alice) understands.
|
* 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
|
* Other methods may be defined in the future
|
||||||
*/
|
*/
|
||||||
@Json(name = "short_authentication_string")
|
@Json(name = "short_authentication_string")
|
||||||
var shortAuthenticationStrings: List<String>? = null
|
override var shortAuthenticationStrings: List<String>? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERIF_METHOD_SAS = "m.sas.v1"
|
const val VERIF_METHOD_SAS = "m.sas.v1"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (transactionID.isNullOrBlank()
|
if (transactionID.isNullOrBlank()
|
||||||
|| fromDevice.isNullOrBlank()
|
|| fromDevice.isNullOrBlank()
|
||||||
|| method != VERIF_METHOD_SAS
|
|| method != VERIF_METHOD_SAS
|
||||||
|
@ -95,4 +101,6 @@ class KeyVerificationStart : SendToDeviceObject {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toSendToDeviceObject() = this
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<EncryptEventTask.Params, Event> {
|
||||||
|
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<String>? = 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<MXEncryptEventContentResult> {
|
||||||
|
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
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<RequestVerificationDMTask.Params, SendResponse> {
|
||||||
|
data class Params(
|
||||||
|
val roomId: String,
|
||||||
|
val from: String,
|
||||||
|
val methods: List<String>,
|
||||||
|
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<SendResponse> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<SendVerificationMessageTask.Params, SendResponse> {
|
||||||
|
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<SendResponse> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
import android.util.Base64
|
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.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
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.crypto.sas.SasVerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.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
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class IncomingSASVerificationTransaction(
|
internal class DefaultIncomingSASVerificationTransaction(
|
||||||
private val sasVerificationService: DefaultSasVerificationService,
|
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
override val credentials: Credentials,
|
||||||
private val credentials: Credentials,
|
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
deviceFingerprint: String,
|
deviceFingerprint: String,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
otherUserID: String)
|
otherUserID: String
|
||||||
: SASVerificationTransaction(
|
) : SASVerificationTransaction(
|
||||||
sasVerificationService,
|
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
credentials,
|
credentials,
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
|
||||||
taskExecutor,
|
|
||||||
deviceFingerprint,
|
deviceFingerprint,
|
||||||
transactionId,
|
transactionId,
|
||||||
otherUserID,
|
otherUserID,
|
||||||
|
@ -78,10 +66,10 @@ internal class IncomingSASVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVerificationStart(startReq: KeyVerificationStart) {
|
override fun onVerificationStart(startReq: VerifInfoStart) {
|
||||||
Timber.v("## SAS received verification request from state $state")
|
Timber.v("## SAS I: received verification request from state $state")
|
||||||
if (state != SasVerificationTxState.None) {
|
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??
|
// should I cancel??
|
||||||
throw IllegalStateException("Interactive Key verification already started")
|
throw IllegalStateException("Interactive Key verification already started")
|
||||||
}
|
}
|
||||||
|
@ -92,7 +80,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
|
|
||||||
override fun performAccept() {
|
override fun performAccept() {
|
||||||
if (state != SasVerificationTxState.OnStarted) {
|
if (state != SasVerificationTxState.OnStarted) {
|
||||||
Timber.e("## Cannot perform accept from state $state")
|
Timber.e("## SAS Cannot perform accept from state $state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +97,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() }
|
if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() }
|
||||||
|| agreedShortCode.isNullOrEmpty()) {
|
|| agreedShortCode.isNullOrEmpty()) {
|
||||||
// Failed to find agreement
|
// Failed to find agreement
|
||||||
Timber.e("## Failed to find agreement ")
|
Timber.e("## SAS Failed to find agreement ")
|
||||||
cancel(CancelCode.UnknownMethod)
|
cancel(CancelCode.UnknownMethod)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -118,7 +106,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
val mxDeviceInfo = cryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId)
|
val mxDeviceInfo = cryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId)
|
||||||
|
|
||||||
if (mxDeviceInfo?.fingerprint() == null) {
|
if (mxDeviceInfo?.fingerprint() == null) {
|
||||||
Timber.e("## Failed to find device key ")
|
Timber.e("## SAS Failed to find device key ")
|
||||||
// TODO force download keys!!
|
// TODO force download keys!!
|
||||||
// would be probably better to download the keys
|
// would be probably better to download the keys
|
||||||
// for now I cancel
|
// for now I cancel
|
||||||
|
@ -126,7 +114,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
} else {
|
} else {
|
||||||
// val otherKey = info.identityKey()
|
// val otherKey = info.identityKey()
|
||||||
// need to jump back to correct thread
|
// need to jump back to correct thread
|
||||||
val accept = KeyVerificationAccept.create(
|
val accept = transport.createAccept(
|
||||||
tid = transactionId,
|
tid = transactionId,
|
||||||
keyAgreementProtocol = agreedProtocol!!,
|
keyAgreementProtocol = agreedProtocol!!,
|
||||||
hash = agreedHash!!,
|
hash = agreedHash!!,
|
||||||
|
@ -138,13 +126,13 @@ internal class IncomingSASVerificationTransaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doAccept(accept: KeyVerificationAccept) {
|
private fun doAccept(accept: VerifInfoAccept) {
|
||||||
this.accepted = accept
|
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,
|
// 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
|
// 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) ?: ""
|
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||||
// we need to send this to other device now
|
// we need to send this to other device now
|
||||||
state = SasVerificationTxState.SendingAccept
|
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")
|
Timber.v("## SAS invalid message for incoming request id:$transactionId")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
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")
|
Timber.v("## SAS received key for request id:$transactionId")
|
||||||
if (state != SasVerificationTxState.SendingAccept && state != SasVerificationTxState.Accepted) {
|
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)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -175,7 +163,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
// sending Bob’s public key QB
|
// sending Bob’s public key QB
|
||||||
val pubKey = getSAS().publicKey
|
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
|
// we need to send this to other device now
|
||||||
state = SasVerificationTxState.SendingKey
|
state = SasVerificationTxState.SendingKey
|
||||||
this.sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) {
|
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.
|
// emoji: generate six bytes by using HKDF.
|
||||||
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
|
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
|
||||||
|
|
||||||
Timber.e("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
|
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||||
Timber.e("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}")
|
Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
|
||||||
|
Timber.v("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}")
|
||||||
|
}
|
||||||
|
|
||||||
state = SasVerificationTxState.ShortCodeReady
|
state = SasVerificationTxState.ShortCodeReady
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationMac(vKey: KeyVerificationMac) {
|
override fun onKeyVerificationMac(vKey: VerifInfoMac) {
|
||||||
Timber.v("## SAS received mac for request id:$transactionId")
|
Timber.v("## SAS I: received mac for request id:$transactionId")
|
||||||
// Check for state?
|
// Check for state?
|
||||||
if (state != SasVerificationTxState.SendingKey
|
if (state != SasVerificationTxState.SendingKey
|
||||||
&& state != SasVerificationTxState.KeySent
|
&& state != SasVerificationTxState.KeySent
|
||||||
|
@ -221,7 +211,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
&& state != SasVerificationTxState.ShortCodeAccepted
|
&& state != SasVerificationTxState.ShortCodeAccepted
|
||||||
&& state != SasVerificationTxState.SendingMac
|
&& state != SasVerificationTxState.SendingMac
|
||||||
&& state != SasVerificationTxState.MacSent) {
|
&& state != SasVerificationTxState.MacSent) {
|
||||||
Timber.e("## received key from invalid state $state")
|
Timber.e("## SAS I: received key from invalid state $state")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -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.crypto.sas.SasVerificationTxState
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.model.rest.KeyVerificationStart
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
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
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class OutgoingSASVerificationRequest(
|
internal class DefaultOutgoingSASVerificationRequest(
|
||||||
private val sasVerificationService: DefaultSasVerificationService,
|
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
credentials: Credentials,
|
||||||
private val credentials: Credentials,
|
cryptoStore: IMXCryptoStore,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
deviceFingerprint: String,
|
deviceFingerprint: String,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String)
|
otherDeviceId: String
|
||||||
: SASVerificationTransaction(
|
) : SASVerificationTransaction(
|
||||||
sasVerificationService,
|
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
credentials,
|
credentials,
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
|
||||||
taskExecutor,
|
|
||||||
deviceFingerprint,
|
deviceFingerprint,
|
||||||
transactionId,
|
transactionId,
|
||||||
otherUserId,
|
otherUserId,
|
||||||
|
@ -78,14 +66,14 @@ internal class OutgoingSASVerificationRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVerificationStart(startReq: KeyVerificationStart) {
|
override fun onVerificationStart(startReq: VerifInfoStart) {
|
||||||
Timber.e("## onVerificationStart - unexpected id:$transactionId")
|
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (state != SasVerificationTxState.None) {
|
if (state != SasVerificationTxState.None) {
|
||||||
Timber.e("## start verification from invalid state")
|
Timber.e("## SAS O: start verification from invalid state")
|
||||||
// should I cancel??
|
// should I cancel??
|
||||||
throw IllegalStateException("Interactive Key verification already started")
|
throw IllegalStateException("Interactive Key verification already started")
|
||||||
}
|
}
|
||||||
|
@ -111,10 +99,33 @@ internal class OutgoingSASVerificationRequest(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVerificationAccept(accept: KeyVerificationAccept) {
|
// fun request() {
|
||||||
Timber.v("## onVerificationAccept id:$transactionId")
|
// 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) {
|
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)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -123,7 +134,7 @@ internal class OutgoingSASVerificationRequest(
|
||||||
|| !KNOWN_HASHES.contains(accept.hash)
|
|| !KNOWN_HASHES.contains(accept.hash)
|
||||||
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
||||||
|| accept.shortAuthenticationStrings!!.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
|| 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)
|
cancel(CancelCode.UnknownMethod)
|
||||||
return
|
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
|
// 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 pubKey = getSAS().publicKey
|
||||||
|
|
||||||
val keyToDevice = KeyVerificationKey.create(transactionId, pubKey)
|
val keyToDevice = transport.createKey(transactionId, pubKey)
|
||||||
// we need to send this to other device now
|
// we need to send this to other device now
|
||||||
state = SasVerificationTxState.SendingKey
|
state = SasVerificationTxState.SendingKey
|
||||||
sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) {
|
sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) {
|
||||||
|
@ -148,8 +159,8 @@ internal class OutgoingSASVerificationRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) {
|
override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) {
|
||||||
Timber.v("## onKeyVerificationKey id:$transactionId")
|
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
|
||||||
if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent) {
|
if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent) {
|
||||||
Timber.e("## received key from invalid state $state")
|
Timber.e("## received key from invalid state $state")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
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.
|
// in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
||||||
|
|
||||||
// check commitment
|
// check commitment
|
||||||
val concat = vKey.key + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
|
val concat = vKey.key + startReq!!.toCanonicalJson()
|
||||||
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||||
|
|
||||||
if (accepted!!.commitment.equals(otherCommitment)) {
|
if (accepted!!.commitment.equals(otherCommitment)) {
|
||||||
|
@ -190,14 +201,14 @@ internal class OutgoingSASVerificationRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyVerificationMac(vKey: KeyVerificationMac) {
|
override fun onKeyVerificationMac(vKey: VerifInfoMac) {
|
||||||
Timber.v("## onKeyVerificationMac id:$transactionId")
|
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
||||||
if (state != SasVerificationTxState.OnKeyReceived
|
if (state != SasVerificationTxState.OnKeyReceived
|
||||||
&& state != SasVerificationTxState.ShortCodeReady
|
&& state != SasVerificationTxState.ShortCodeReady
|
||||||
&& state != SasVerificationTxState.ShortCodeAccepted
|
&& state != SasVerificationTxState.ShortCodeAccepted
|
||||||
&& state != SasVerificationTxState.SendingMac
|
&& state != SasVerificationTxState.SendingMac
|
||||||
&& state != SasVerificationTxState.MacSent) {
|
&& state != SasVerificationTxState.MacSent) {
|
||||||
Timber.e("## received key from invalid state $state")
|
Timber.e("## SAS O: received key from invalid state $state")
|
||||||
cancel(CancelCode.UnexpectedMessage)
|
cancel(CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ import android.os.Looper
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
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.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
|
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.DeviceListManager
|
||||||
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
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.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
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.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.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.Exception
|
import java.util.*
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.collections.set
|
||||||
/**
|
|
||||||
* 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).
|
|
||||||
*/
|
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials,
|
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 deviceListManager: DeviceListManager,
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
|
private val requestVerificationDMTask: DefaultRequestVerificationDMTask,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory,
|
||||||
|
private val sasToDeviceTransportFactory: SasToDeviceTransportFactory,
|
||||||
private val taskExecutor: TaskExecutor)
|
private val taskExecutor: TaskExecutor)
|
||||||
: VerificationTransaction.Listener, SasVerificationService {
|
: VerificationTransaction.Listener, SasVerificationService {
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
// Cannot be injected in constructor as it creates a dependency cycle
|
||||||
|
lateinit var cryptoService: CryptoService
|
||||||
|
|
||||||
// map [sender : [transaction]]
|
// map [sender : [transaction]]
|
||||||
private val txMap = HashMap<String, HashMap<String, VerificationTransaction>>()
|
private val txMap = HashMap<String, HashMap<String, VerificationTransaction>>()
|
||||||
|
|
||||||
|
@ -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<MessageContent>()?.type) {
|
||||||
|
onRoomRequestReceived(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var listeners = ArrayList<SasVerificationService.SasVerificationListener>()
|
private var listeners = ArrayList<SasVerificationService.SasVerificationListener>()
|
||||||
|
|
||||||
override fun addListener(listener: SasVerificationService.SasVerificationListener) {
|
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<MessageVerificationStartContent>()
|
||||||
|
?.copy(
|
||||||
|
// relates_to is in clear in encrypted payload
|
||||||
|
relatesTo = event.content.toModel<MessageRelationContent>()?.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) {
|
private suspend fun onStartRequestReceived(event: Event) {
|
||||||
|
Timber.e("## SAS received Start request ${event.eventId}")
|
||||||
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
||||||
|
Timber.v("## SAS received Start request $startReq")
|
||||||
|
|
||||||
val otherUserId = event.senderId
|
val otherUserId = event.senderId
|
||||||
if (!startReq.isValid()) {
|
if (!startReq.isValid()) {
|
||||||
Timber.e("## received invalid verification request")
|
Timber.e("## SAS received invalid verification request")
|
||||||
if (startReq.transactionID != null) {
|
if (startReq.transactionID != null) {
|
||||||
cancelTransaction(
|
// cancelTransaction(
|
||||||
startReq.transactionID!!,
|
// startReq.transactionID!!,
|
||||||
|
// otherUserId!!,
|
||||||
|
// startReq.fromDevice ?: event.getSenderKey()!!,
|
||||||
|
// CancelCode.UnknownMethod
|
||||||
|
// )
|
||||||
|
sasToDeviceTransportFactory.createTransport(null).cancelTransaction(
|
||||||
|
startReq.transactionID ?: "",
|
||||||
otherUserId!!,
|
otherUserId!!,
|
||||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||||
CancelCode.UnknownMethod
|
CancelCode.UnknownMethod
|
||||||
|
@ -167,8 +256,22 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Download device keys prior to everything
|
// 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) {
|
if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) {
|
||||||
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
|
Timber.v("## SAS onStartRequestReceived $startReq")
|
||||||
val tid = startReq.transactionID!!
|
val tid = startReq.transactionID!!
|
||||||
val existing = getExistingTransaction(otherUserId, tid)
|
val existing = getExistingTransaction(otherUserId, tid)
|
||||||
val existingTxs = getExistingTransactionsForUser(otherUserId)
|
val existingTxs = getExistingTransactionsForUser(otherUserId)
|
||||||
|
@ -176,43 +279,46 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
// should cancel both!
|
// should cancel both!
|
||||||
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
|
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
|
||||||
existing.cancel(CancelCode.UnexpectedMessage)
|
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) {
|
} else if (existingTxs?.isEmpty() == false) {
|
||||||
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
|
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.
|
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
|
||||||
existingTxs.forEach {
|
existingTxs.forEach {
|
||||||
it.cancel(CancelCode.UnexpectedMessage)
|
it.cancel(CancelCode.UnexpectedMessage)
|
||||||
}
|
}
|
||||||
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
return CancelCode.UnexpectedMessage
|
||||||
|
// cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||||
} else {
|
} else {
|
||||||
// Ok we can create
|
// Ok we can create
|
||||||
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
||||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||||
val tx = IncomingSASVerificationTransaction(
|
val tx = DefaultIncomingSASVerificationTransaction(
|
||||||
this,
|
// this,
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
credentials,
|
credentials,
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
|
||||||
taskExecutor,
|
|
||||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
startReq.transactionID!!,
|
startReq.transactionID!!,
|
||||||
otherUserId)
|
otherUserId).also { txConfigure(it) }
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
tx.acceptToDeviceEvent(otherUserId, startReq)
|
tx.acceptVerificationEvent(otherUserId, startReq)
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
||||||
cancelTransaction(tid, otherUserId, startReq.fromDevice
|
return CancelCode.UnknownMethod
|
||||||
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
|
// cancelTransaction(tid, otherUserId, startReq.fromDevice
|
||||||
|
// ?: event.getSenderKey()!!, CancelCode.UnknownMethod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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,
|
private suspend fun checkKeysAreDownloaded(otherUserId: String,
|
||||||
startReq: KeyVerificationStart): MXUsersDevicesMap<MXDeviceInfo>? {
|
startReq: VerifInfoStart): MXUsersDevicesMap<MXDeviceInfo>? {
|
||||||
return try {
|
return try {
|
||||||
val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
|
val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
|
||||||
val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null
|
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<MessageVerificationCancelContent>()
|
||||||
|
?.copy(
|
||||||
|
// relates_to is in clear in encrypted payload
|
||||||
|
relatesTo = event.content.toModel<MessageRelationContent>()?.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")
|
Timber.v("## SAS onCancelReceived")
|
||||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!
|
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!
|
||||||
|
|
||||||
if (!cancelReq.isValid()) {
|
if (!cancelReq.isValid()) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## SAS Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.senderId!!
|
val otherUserId = event.senderId!!
|
||||||
|
|
||||||
|
handleOnCancel(otherUserId, cancelReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleOnCancel(otherUserId: String, cancelReq: VerifInfoCancel) {
|
||||||
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
||||||
val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
|
@ -245,65 +370,119 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onAcceptReceived(event: Event) {
|
private fun onRoomAcceptReceived(event: Event) {
|
||||||
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>()!!
|
Timber.d("## SAS Received Accept via DM $event")
|
||||||
|
val accept = event.getClearContent().toModel<MessageVerificationAcceptContent>()
|
||||||
|
?.copy(
|
||||||
|
// relates_to is in clear in encrypted payload
|
||||||
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
|
)
|
||||||
|
?: return
|
||||||
|
handleAccept(accept, event.senderId!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAcceptReceived(event: Event) {
|
||||||
|
Timber.d("## SAS Received Accept $event")
|
||||||
|
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>() ?: return
|
||||||
|
handleAccept(acceptReq, event.senderId!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAccept(acceptReq: VerifInfoAccept, senderId: String) {
|
||||||
if (!acceptReq.isValid()) {
|
if (!acceptReq.isValid()) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## SAS Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.senderId!!
|
val otherUserId = senderId
|
||||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## SAS Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing is SASVerificationTransaction) {
|
if (existing is SASVerificationTransaction) {
|
||||||
existing.acceptToDeviceEvent(otherUserId, acceptReq)
|
existing.acceptVerificationEvent(otherUserId, acceptReq)
|
||||||
} else {
|
} else {
|
||||||
// not other types now
|
// not other types now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onKeyReceived(event: Event) {
|
private fun onRoomKeyRequestReceived(event: Event) {
|
||||||
|
val keyReq = event.getClearContent().toModel<MessageVerificationKeyContent>()
|
||||||
|
?.copy(
|
||||||
|
// relates_to is in clear in encrypted payload
|
||||||
|
relatesTo = event.content.toModel<MessageRelationContent>()?.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<KeyVerificationKey>()!!
|
val keyReq = event.getClearContent().toModel<KeyVerificationKey>()!!
|
||||||
|
|
||||||
if (!keyReq.isValid()) {
|
if (!keyReq.isValid()) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## Received invalid key request")
|
Timber.e("## SAS Received invalid key request")
|
||||||
return
|
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 otherUserId = event.senderId!!
|
||||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## SAS Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (existing is SASVerificationTransaction) {
|
if (existing is SASVerificationTransaction) {
|
||||||
existing.acceptToDeviceEvent(otherUserId, keyReq)
|
existing.acceptVerificationEvent(otherUserId, keyReq)
|
||||||
} else {
|
} else {
|
||||||
// not other types now
|
// not other types now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onMacReceived(event: Event) {
|
private fun onRoomMacReceived(event: Event) {
|
||||||
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!
|
val macReq = event.getClearContent().toModel<MessageVerificationMacContent>()
|
||||||
|
?.copy(
|
||||||
if (!macReq.isValid()) {
|
// relates_to is in clear in encrypted payload
|
||||||
|
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||||
|
)
|
||||||
|
if (macReq == null || macReq.isValid().not() || event.senderId == null) {
|
||||||
// ignore
|
// ignore
|
||||||
Timber.e("## Received invalid key request")
|
Timber.e("## SAS Received invalid mac request")
|
||||||
|
// TODO should we cancel?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.senderId!!
|
handleMacReceived(event.senderId, macReq)
|
||||||
val existing = getExistingTransaction(otherUserId, macReq.transactionID!!)
|
}
|
||||||
|
|
||||||
|
private fun onMacReceived(event: Event) {
|
||||||
|
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!
|
||||||
|
|
||||||
|
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) {
|
if (existing == null) {
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## SAS Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (existing is SASVerificationTransaction) {
|
if (existing is SASVerificationTransaction) {
|
||||||
existing.acceptToDeviceEvent(otherUserId, macReq)
|
existing.acceptVerificationEvent(senderId, macReq)
|
||||||
} else {
|
} else {
|
||||||
// not other types known for now
|
// not other types known for now
|
||||||
}
|
}
|
||||||
|
@ -346,13 +525,10 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
val txID = createUniqueIDForTransaction(userId, deviceID)
|
val txID = createUniqueIDForTransaction(userId, deviceID)
|
||||||
// should check if already one (and cancel it)
|
// should check if already one (and cancel it)
|
||||||
if (KeyVerificationStart.VERIF_METHOD_SAS == method) {
|
if (KeyVerificationStart.VERIF_METHOD_SAS == method) {
|
||||||
val tx = OutgoingSASVerificationRequest(
|
val tx = DefaultOutgoingSASVerificationRequest(
|
||||||
this,
|
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
credentials,
|
credentials,
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
|
||||||
taskExecutor,
|
|
||||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
txID,
|
txID,
|
||||||
userId,
|
userId,
|
||||||
|
@ -366,6 +542,30 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?) {
|
||||||
|
requestVerificationDMTask.configureWith(
|
||||||
|
RequestVerificationDMTask.Params(
|
||||||
|
roomId = roomId,
|
||||||
|
from = credentials.deviceId ?: "",
|
||||||
|
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||||
|
to = userId,
|
||||||
|
cryptoService = cryptoService
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.callback = object : MatrixCallback<SendResponse> {
|
||||||
|
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
|
* 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)
|
this.removeTransaction(tx.otherUserId, tx.transactionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) {
|
// fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode, roomId: String? = null) {
|
||||||
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
// val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
// val contentMap = MXUsersDevicesMap<Any>()
|
||||||
contentMap.setObject(userId, userDevice, cancelMessage)
|
// contentMap.setObject(userId, userDevice, cancelMessage)
|
||||||
|
//
|
||||||
sendToDeviceTask
|
// if (roomId != null) {
|
||||||
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
|
//
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
// } else {
|
||||||
override fun onSuccess(data: Unit) {
|
// sendToDeviceTask
|
||||||
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
|
// .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
|
||||||
}
|
// this.callback = object : MatrixCallback<Unit> {
|
||||||
|
// override fun onSuccess(data: Unit) {
|
||||||
override fun onFailure(failure: Throwable) {
|
// Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
|
||||||
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
|
// }
|
||||||
}
|
//
|
||||||
}
|
// override fun onFailure(failure: Throwable) {
|
||||||
}
|
// Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
|
||||||
.executeBy(taskExecutor)
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
// .executeBy(taskExecutor)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
import android.os.Build
|
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.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
|
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.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
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.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.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.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.OlmSAS
|
||||||
import org.matrix.olm.OlmUtility
|
import org.matrix.olm.OlmUtility
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -42,12 +36,9 @@ import kotlin.properties.Delegates
|
||||||
* Represents an ongoing short code interactive key verification between two devices.
|
* Represents an ongoing short code interactive key verification between two devices.
|
||||||
*/
|
*/
|
||||||
internal abstract class SASVerificationTransaction(
|
internal abstract class SASVerificationTransaction(
|
||||||
private val sasVerificationService: DefaultSasVerificationService,
|
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
private val credentials: Credentials,
|
open val credentials: Credentials,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val deviceFingerprint: String,
|
private val deviceFingerprint: String,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
|
@ -55,6 +46,8 @@ internal abstract class SASVerificationTransaction(
|
||||||
isIncoming: Boolean) :
|
isIncoming: Boolean) :
|
||||||
VerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming) {
|
VerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming) {
|
||||||
|
|
||||||
|
lateinit var transport: SasTransport
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||||
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
|
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
|
||||||
|
@ -95,13 +88,13 @@ internal abstract class SASVerificationTransaction(
|
||||||
|
|
||||||
private var olmSas: OlmSAS? = null
|
private var olmSas: OlmSAS? = null
|
||||||
|
|
||||||
var startReq: KeyVerificationStart? = null
|
var startReq: VerifInfoStart? = null
|
||||||
var accepted: KeyVerificationAccept? = null
|
var accepted: VerifInfoAccept? = null
|
||||||
var otherKey: String? = null
|
var otherKey: String? = null
|
||||||
var shortCodeBytes: ByteArray? = null
|
var shortCodeBytes: ByteArray? = null
|
||||||
|
|
||||||
var myMac: KeyVerificationMac? = null
|
var myMac: VerifInfoMac? = null
|
||||||
var theirMac: KeyVerificationMac? = null
|
var theirMac: VerifInfoMac? = null
|
||||||
|
|
||||||
fun getSAS(): OlmSAS {
|
fun getSAS(): OlmSAS {
|
||||||
if (olmSas == null) olmSas = OlmSAS()
|
if (olmSas == null) olmSas = OlmSAS()
|
||||||
|
@ -160,7 +153,7 @@ internal abstract class SASVerificationTransaction(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val macMsg = KeyVerificationMac.create(transactionId, mapOf(keyId to macString), keyStrings)
|
val macMsg = transport.createMac(transactionId, mapOf(keyId to macString), keyStrings)
|
||||||
myMac = macMsg
|
myMac = macMsg
|
||||||
state = SasVerificationTxState.SendingMac
|
state = SasVerificationTxState.SendingMac
|
||||||
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, SasVerificationTxState.MacSent, CancelCode.User) {
|
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, SasVerificationTxState.MacSent, CancelCode.User) {
|
||||||
|
@ -176,25 +169,25 @@ internal abstract class SASVerificationTransaction(
|
||||||
} // if not wait for it
|
} // if not wait for it
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject) {
|
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||||
when (event) {
|
when (info) {
|
||||||
is KeyVerificationStart -> onVerificationStart(event)
|
is VerifInfoStart -> onVerificationStart(info)
|
||||||
is KeyVerificationAccept -> onVerificationAccept(event)
|
is VerifInfoAccept -> onVerificationAccept(info)
|
||||||
is KeyVerificationKey -> onKeyVerificationKey(senderId, event)
|
is VerifInfoKey -> onKeyVerificationKey(senderId, info)
|
||||||
is KeyVerificationMac -> onKeyVerificationMac(event)
|
is VerifInfoMac -> onKeyVerificationMac(info)
|
||||||
else -> {
|
else -> {
|
||||||
// nop
|
// 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() {
|
protected fun verifyMacs() {
|
||||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
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
|
// if none of the keys could be verified, then error because the app
|
||||||
// should be informed about that
|
// should be informed about that
|
||||||
if (verifiedDevices.isEmpty()) {
|
if (verifiedDevices.isEmpty()) {
|
||||||
Timber.e("Verification: No devices verified")
|
Timber.e("## SAS Verification: No devices verified")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -254,6 +247,7 @@ internal abstract class SASVerificationTransaction(
|
||||||
verifiedDevices.forEach {
|
verifiedDevices.forEach {
|
||||||
setDeviceVerified(it, otherUserId)
|
setDeviceVerified(it, otherUserId)
|
||||||
}
|
}
|
||||||
|
transport.done(transactionId)
|
||||||
state = SasVerificationTxState.Verified
|
state = SasVerificationTxState.Verified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,41 +264,15 @@ internal abstract class SASVerificationTransaction(
|
||||||
override fun cancel(code: CancelCode) {
|
override fun cancel(code: CancelCode) {
|
||||||
cancelledReason = code
|
cancelledReason = code
|
||||||
state = SasVerificationTxState.Cancelled
|
state = SasVerificationTxState.Cancelled
|
||||||
sasVerificationService.cancelTransaction(
|
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
||||||
transactionId,
|
|
||||||
otherUserId,
|
|
||||||
otherDeviceId ?: "",
|
|
||||||
code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun sendToOther(type: String,
|
protected fun sendToOther(type: String,
|
||||||
keyToDevice: Any,
|
keyToDevice: VerificationInfo,
|
||||||
nextState: SasVerificationTxState,
|
nextState: SasVerificationTxState,
|
||||||
onErrorReason: CancelCode,
|
onErrorReason: CancelCode,
|
||||||
onDone: (() -> Unit)?) {
|
onDone: (() -> Unit)?) {
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
||||||
contentMap.setObject(otherUserId, otherDeviceId, keyToDevice)
|
|
||||||
|
|
||||||
sendToDeviceTask
|
|
||||||
.configureWith(SendToDeviceTask.Params(type, contentMap, transactionId)) {
|
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getShortCodeRepresentation(shortAuthenticationStringMode: String): String? {
|
fun getShortCodeRepresentation(shortAuthenticationStringMode: String): String? {
|
||||||
|
|
|
@ -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<String>): VerifInfoAccept
|
||||||
|
|
||||||
|
fun createKey(tid: String,
|
||||||
|
pubKey: String): VerifInfoKey
|
||||||
|
|
||||||
|
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerifInfoMac
|
||||||
|
}
|
|
@ -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<SendResponse> {
|
||||||
|
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<String>)
|
||||||
|
: 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<String, String>, 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Any>()
|
||||||
|
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<Unit> {
|
||||||
|
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<Any>()
|
||||||
|
contentMap.setObject(userId, userDevice, cancelMessage)
|
||||||
|
sendToDeviceTask
|
||||||
|
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
|
||||||
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
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<String>)
|
||||||
|
: 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<String, String>, 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String>): VerifInfoAccept
|
||||||
|
}
|
|
@ -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?
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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<String, String>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String, String>, keys: String) : VerifInfoMac
|
||||||
|
}
|
|
@ -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<String>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of hashes that Alice’s client understands.
|
||||||
|
* Must include “sha256”. Other methods may be defined in the future.
|
||||||
|
*/
|
||||||
|
val hashes: List<String>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String>?
|
||||||
|
|
||||||
|
fun toCanonicalJson(): String?
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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<EventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
|
override val query = Monarchy.Query<EventEntity> {
|
||||||
|
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<EventEntity>, 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<MessageContent>()?.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
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
|
* Generic interactive key verification transaction
|
||||||
|
@ -42,7 +41,7 @@ internal abstract class VerificationTransaction(
|
||||||
listeners.remove(listener)
|
listeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject)
|
abstract fun acceptVerificationEvent(senderId: String, info: VerificationInfo)
|
||||||
|
|
||||||
abstract fun cancel(code: CancelCode)
|
abstract fun cancel(code: CancelCode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
|
||||||
|
|
||||||
return Failure.ServerError(matrixError, httpCode)
|
return Failure.ServerError(matrixError, httpCode)
|
||||||
}
|
}
|
||||||
} catch (ex: JsonDataException) {
|
} catch (ex: Exception) {
|
||||||
// This is not a MatrixError
|
// This is not a MatrixError
|
||||||
Timber.w("The error returned by the server is not a MatrixError")
|
Timber.w("The error returned by the server is not a MatrixError")
|
||||||
} catch (ex: JsonEncodingException) {
|
} catch (ex: JsonEncodingException) {
|
||||||
|
|
|
@ -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.Session
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
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.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
||||||
import im.vector.matrix.android.internal.di.*
|
import im.vector.matrix.android.internal.di.*
|
||||||
|
@ -164,6 +165,10 @@ internal abstract class SessionModule {
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver
|
abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindVerificationEventObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService
|
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,8 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
|
||||||
|
|
||||||
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
||||||
monarchy.writeAsync { realm ->
|
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()
|
it.deleteFromRealm()
|
||||||
}
|
}
|
||||||
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let {
|
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let {
|
||||||
|
|
|
@ -286,6 +286,24 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createVerificationRequest(roomId: String, fromDevice: String, to: String, methods: List<String>): 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 {
|
private fun dummyOriginServerTs(): Long {
|
||||||
return System.currentTimeMillis()
|
return System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
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.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarc
|
||||||
|
|
||||||
suspend fun updateSendState(eventId: String, sendState: SendState) {
|
suspend fun updateSendState(eventId: String, sendState: SendState) {
|
||||||
Timber.v("Update local state of $eventId to ${sendState.name}")
|
Timber.v("Update local state of $eventId to ${sendState.name}")
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.writeAsync { realm ->
|
||||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||||
if (sendingEventEntity != null) {
|
if (sendingEventEntity != null) {
|
||||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||||
|
|
|
@ -17,4 +17,7 @@
|
||||||
<string name="notice_room_withdraw_with_reason">%1$s withdrew %2$s\'s invitation. Reason: %3$s</string>
|
<string name="notice_room_withdraw_with_reason">%1$s withdrew %2$s\'s invitation. Reason: %3$s</string>
|
||||||
|
|
||||||
<string name="no_network_indicator">There is no network connection right now</string>
|
<string name="no_network_indicator">There is no network connection right now</string>
|
||||||
|
|
||||||
|
<string name="key_verification_request_fallback_message">%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.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -207,6 +207,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(VectorSettingsNotificationPreferenceFragment::class)
|
@FragmentKey(VectorSettingsNotificationPreferenceFragment::class)
|
||||||
fun bindVectorSettingsNotificationPreferenceFragment(fragment: VectorSettingsNotificationPreferenceFragment): Fragment
|
fun bindVectorSettingsNotificationPreferenceFragment(fragment: VectorSettingsNotificationPreferenceFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(VectorSettingsLabsFragment::class)
|
||||||
|
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(VectorSettingsPreferencesFragment::class)
|
@FragmentKey(VectorSettingsPreferencesFragment::class)
|
||||||
|
|
|
@ -38,7 +38,9 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
||||||
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
|
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
|
||||||
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
|
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
|
||||||
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
||||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler);
|
SHRUG("/shrug", "<message>", R.string.command_description_shrug),
|
||||||
|
// TODO temporary command
|
||||||
|
VERIFY_USER("/verify", "<userID>", R.string.command_description_spoiler);
|
||||||
|
|
||||||
val length
|
val length
|
||||||
get() = command.length + 1
|
get() = command.length + 1
|
||||||
|
|
|
@ -244,6 +244,17 @@ object CommandParser {
|
||||||
|
|
||||||
ParsedCommand.SendSpoiler(message)
|
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 -> {
|
else -> {
|
||||||
// Unknown command
|
// Unknown command
|
||||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||||
|
|
|
@ -46,4 +46,6 @@ sealed class ParsedCommand {
|
||||||
class SetMarkdown(val enable: Boolean) : ParsedCommand()
|
class SetMarkdown(val enable: Boolean) : ParsedCommand()
|
||||||
object ClearScalarToken : ParsedCommand()
|
object ClearScalarToken : ParsedCommand()
|
||||||
class SendSpoiler(val message: String) : ParsedCommand()
|
class SendSpoiler(val message: String) : ParsedCommand()
|
||||||
|
class SendShrug(val message: CharSequence) : ParsedCommand()
|
||||||
|
class VerifyUser(val userId: String) : ParsedCommand()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.BuildConfig
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.postLiveEvent
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
@ -377,6 +376,25 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||||
popDraft()
|
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 -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(slashCommandResult)
|
handleChangeTopicSlashCommand(slashCommandResult)
|
||||||
popDraft()
|
popDraft()
|
||||||
|
|
|
@ -64,6 +64,16 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
encryptedItemFactory.create(event, nextEvent, highlight, callback)
|
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)
|
// Unhandled event types (yet)
|
||||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback)
|
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback)
|
||||||
|
|
|
@ -42,7 +42,13 @@ object TimelineDisplayableEvents {
|
||||||
|
|
||||||
val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf(
|
val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf(
|
||||||
EventType.REDACTION,
|
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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import im.vector.riotx.BuildConfig
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
@ -256,7 +257,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun labAllowedExtendedLogging(): Boolean {
|
fun labAllowedExtendedLogging(): Boolean {
|
||||||
return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
|
return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, BuildConfig.DEBUG)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,14 +17,21 @@
|
||||||
package im.vector.riotx.features.settings
|
package im.vector.riotx.features.settings
|
||||||
|
|
||||||
import im.vector.riotx.R
|
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 var titleRes = R.string.room_settings_labs_pref_title
|
||||||
override val preferenceXmlRes = R.xml.vector_settings_labs
|
override val preferenceXmlRes = R.xml.vector_settings_labs
|
||||||
|
|
||||||
override fun bindPref() {
|
override fun bindPref() {
|
||||||
// Lab
|
// Lab
|
||||||
|
|
||||||
|
findPreference<VectorSwitchPreference>(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 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)
|
// val cryptoIsEnabledPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY)
|
||||||
|
|
||||||
|
|
|
@ -1695,6 +1695,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
||||||
<string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
|
<string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
|
||||||
|
|
||||||
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
|
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
|
||||||
|
<string name="labs_enable_verification_other_dm">Enable verification other DM</string>
|
||||||
|
|
||||||
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
|
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
<string name="room_list_quick_actions_leave">"Leave the room"</string>
|
<string name="room_list_quick_actions_leave">"Leave the room"</string>
|
||||||
<string name="notice_member_no_changes">"%1$s made no changes"</string>
|
<string name="notice_member_no_changes">"%1$s made no changes"</string>
|
||||||
<string name="command_description_spoiler">Sends the given message as a spoiler</string>
|
<string name="command_description_spoiler">Sends the given message as a spoiler</string>
|
||||||
|
<string name="command_description_verify">Request to verify the given userID</string>
|
||||||
|
<string name="command_description_shrug">Prepends ¯\\_(ツ)_/¯ to a plain-text message</string>
|
||||||
<string name="spoiler">Spoiler</string>
|
<string name="spoiler">Spoiler</string>
|
||||||
<string name="reaction_search_type_hint">Type keywords to find a reaction.</string>
|
<string name="reaction_search_type_hint">Type keywords to find a reaction.</string>
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,6 @@
|
||||||
android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
||||||
android:title="@string/labs_swipe_to_reply_in_timeline" />
|
android:title="@string/labs_swipe_to_reply_in_timeline" />
|
||||||
|
|
||||||
|
|
||||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
android:key="SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
||||||
|
|
Loading…
Reference in a new issue