Support verification using room transport
This commit is contained in:
parent
853518fbb2
commit
6137a88a6f
|
@ -48,6 +48,7 @@ Changes in RiotX 0.9.0 (2019-12-05)
|
|||
Features ✨:
|
||||
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
||||
- Iteration of the login flow (#613)
|
||||
- [SDK] MSC2241 / verification in DMs (#707)
|
||||
|
||||
Improvements 🙌:
|
||||
- Send mention Pills from composer
|
||||
|
|
|
@ -16,19 +16,42 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
||||
*
|
||||
* Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors.
|
||||
* SAS verification is a user-friendly key verification process.
|
||||
* SAS verification is intended to be a highly interactive process for users,
|
||||
* and as such exposes verification methods which are easier for users to use.
|
||||
*/
|
||||
interface SasVerificationService {
|
||||
|
||||
fun addListener(listener: SasVerificationListener)
|
||||
|
||||
fun removeListener(listener: SasVerificationListener)
|
||||
|
||||
/**
|
||||
* Mark this device as verified manually
|
||||
*/
|
||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||
|
||||
fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction?
|
||||
|
||||
/**
|
||||
* Shortcut for KeyVerificationStart.VERIF_METHOD_SAS
|
||||
* @see beginKeyVerification
|
||||
*/
|
||||
fun beginKeyVerificationSAS(userId: String, deviceID: String): String?
|
||||
|
||||
/**
|
||||
* Request a key verification from another user using toDevice events.
|
||||
*/
|
||||
fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
|
||||
|
||||
fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?)
|
||||
|
||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||
|
||||
interface SasVerificationListener {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface SasVerificationTransaction {
|
||||
val state: SasVerificationTxState
|
||||
var state: SasVerificationTxState
|
||||
|
||||
val cancelledReason: CancelCode?
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ object EventType {
|
|||
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
|
||||
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
||||
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
||||
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
|
||||
|
||||
// Relation Events
|
||||
const val REACTION = "m.reaction"
|
||||
|
|
|
@ -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_LOCATION = "m.location"
|
||||
const val MSGTYPE_FILE = "m.file"
|
||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
|
||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||
// Because sticker isn't a message type but a event type without msgtype field
|
||||
|
|
|
@ -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
|
||||
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
||||
: ClaimOneTimeKeysForUsersDeviceTask
|
||||
|
|
|
@ -136,6 +136,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val cryptoCoroutineScope: CoroutineScope
|
||||
) : CryptoService {
|
||||
|
||||
init {
|
||||
sasVerificationService.cryptoService = this
|
||||
}
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
// MXEncrypting instance for each room.
|
||||
|
|
|
@ -17,13 +17,15 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept
|
||||
import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class KeyVerificationAccept(
|
||||
internal data class KeyVerificationAccept(
|
||||
|
||||
/**
|
||||
* string to identify the transaction.
|
||||
|
@ -31,39 +33,41 @@ data class KeyVerificationAccept(
|
|||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
var transactionID: String? = null,
|
||||
override var transactionID: String? = null,
|
||||
|
||||
/**
|
||||
* The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||
*/
|
||||
@Json(name = "key_agreement_protocol")
|
||||
var keyAgreementProtocol: String? = null,
|
||||
override var keyAgreementProtocol: String? = null,
|
||||
|
||||
/**
|
||||
* The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||
*/
|
||||
var hash: String? = null,
|
||||
@Json(name = "hash")
|
||||
override var hash: String? = null,
|
||||
|
||||
/**
|
||||
* The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device
|
||||
*/
|
||||
@Json(name = "message_authentication_code")
|
||||
var messageAuthenticationCode: String? = null,
|
||||
override var messageAuthenticationCode: String? = null,
|
||||
|
||||
/**
|
||||
* An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device
|
||||
*/
|
||||
@Json(name = "short_authentication_string")
|
||||
var shortAuthenticationStrings: List<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)
|
||||
* and the canonical JSON representation of the m.key.verification.start message.
|
||||
*/
|
||||
var commitment: String? = null
|
||||
) : SendToDeviceObject {
|
||||
@Json(name = "commitment")
|
||||
override var commitment: String? = null
|
||||
) : SendToDeviceObject, VerifInfoAccept {
|
||||
|
||||
fun isValid(): Boolean {
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| keyAgreementProtocol.isNullOrBlank()
|
||||
|| hash.isNullOrBlank()
|
||||
|
@ -76,13 +80,15 @@ data class KeyVerificationAccept(
|
|||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(tid: String,
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
companion object : AcceptVerifInfoFactory {
|
||||
override fun create(tid: String,
|
||||
keyAgreementProtocol: String,
|
||||
hash: String,
|
||||
commitment: String,
|
||||
messageAuthenticationCode: String,
|
||||
shortAuthenticationStrings: List<String>): KeyVerificationAccept {
|
||||
shortAuthenticationStrings: List<String>): VerifInfoAccept {
|
||||
return KeyVerificationAccept().apply {
|
||||
this.transactionID = tid
|
||||
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.JsonClass
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel
|
||||
|
||||
/**
|
||||
* To device event sent by either party to cancel a key verification.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class KeyVerificationCancel(
|
||||
internal data class KeyVerificationCancel(
|
||||
/**
|
||||
* the transaction ID of the verification to cancel
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
var transactionID: String? = null,
|
||||
override val transactionID: String? = null,
|
||||
|
||||
/**
|
||||
* machine-readable reason for cancelling, see #CancelCode
|
||||
*/
|
||||
var code: String? = null,
|
||||
override var code: String? = null,
|
||||
|
||||
/**
|
||||
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
|
||||
*/
|
||||
var reason: String? = null
|
||||
) : SendToDeviceObject {
|
||||
override var reason: String? = null
|
||||
) : SendToDeviceObject, VerifInfoCancel {
|
||||
|
||||
companion object {
|
||||
fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel {
|
||||
return KeyVerificationCancel().apply {
|
||||
this.transactionID = tid
|
||||
this.code = cancelCode.value
|
||||
this.reason = cancelCode.humanReadable
|
||||
}
|
||||
return KeyVerificationCancel(
|
||||
tid,
|
||||
cancelCode.value,
|
||||
cancelCode.humanReadable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -17,37 +17,33 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey
|
||||
|
||||
/**
|
||||
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class KeyVerificationKey(
|
||||
internal data class KeyVerificationKey(
|
||||
/**
|
||||
* the ID of the transaction that the message is part of
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
@JvmField
|
||||
var transactionID: String? = null,
|
||||
@Json(name = "transaction_id") override var transactionID: String? = null,
|
||||
|
||||
/**
|
||||
* The device’s ephemeral public key, as an unpadded base64 string
|
||||
*/
|
||||
@JvmField
|
||||
var key: String? = null
|
||||
@Json(name = "key") override val key: String? = null
|
||||
|
||||
) : SendToDeviceObject {
|
||||
) : SendToDeviceObject, VerifInfoKey {
|
||||
|
||||
companion object {
|
||||
fun create(tid: String, key: String): KeyVerificationKey {
|
||||
return KeyVerificationKey().apply {
|
||||
this.transactionID = tid
|
||||
this.key = key
|
||||
}
|
||||
companion object : KeyVerifInfoFactory {
|
||||
override fun create(tid: String, pubKey: String): KeyVerificationKey {
|
||||
return KeyVerificationKey(tid, pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -17,49 +17,32 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory
|
||||
|
||||
/**
|
||||
* Sent by both devices to send the MAC of their device key to the other device.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class KeyVerificationMac(
|
||||
/**
|
||||
* the ID of the transaction that the message is part of
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
var transactionID: String? = null,
|
||||
internal data class KeyVerificationMac(
|
||||
@Json(name = "transaction_id") override val transactionID: String? = null,
|
||||
@Json(name = "mac") override val mac: Map<String, String>? = null,
|
||||
@Json(name = "key") override val keys: String? = null
|
||||
|
||||
/**
|
||||
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
|
||||
*/
|
||||
@JvmField
|
||||
var mac: Map<String, String>? = null,
|
||||
) : SendToDeviceObject, VerifInfoMac {
|
||||
|
||||
/**
|
||||
* The MAC of the comma-separated, sorted list of key IDs given in the mac property,
|
||||
* as an unpadded base64 string, calculated using the MAC key.
|
||||
* For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will
|
||||
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
|
||||
*/
|
||||
@JvmField
|
||||
var keys: String? = null
|
||||
|
||||
) : SendToDeviceObject {
|
||||
|
||||
fun isValid(): Boolean {
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(tid: String, mac: Map<String, String>, keys: String): KeyVerificationMac {
|
||||
return KeyVerificationMac().apply {
|
||||
this.transactionID = tid
|
||||
this.mac = mac
|
||||
this.keys = keys
|
||||
}
|
||||
override fun toSendToDeviceObject(): SendToDeviceObject? = this
|
||||
|
||||
companion object : VerifInfoMacFactory {
|
||||
override fun create(tid: String, mac: Map<String, String>, keys: String): VerifInfoMac {
|
||||
return KeyVerificationMac(tid, mac, 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 im.vector.matrix.android.api.session.crypto.sas.SasMode
|
||||
import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Sent by Alice to initiate an interactive key verification.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
class KeyVerificationStart : SendToDeviceObject {
|
||||
class KeyVerificationStart : SendToDeviceObject, VerifInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice’s device ID
|
||||
*/
|
||||
@Json(name = "from_device")
|
||||
var fromDevice: String? = null
|
||||
override var fromDevice: String? = null
|
||||
|
||||
var method: String? = null
|
||||
override var method: String? = null
|
||||
|
||||
/**
|
||||
* String to identify the transaction.
|
||||
|
@ -41,7 +47,7 @@ class KeyVerificationStart : SendToDeviceObject {
|
|||
* Alice’s device should record this ID and use it in future messages in this transaction.
|
||||
*/
|
||||
@Json(name = "transaction_id")
|
||||
var transactionID: String? = null
|
||||
override var transactionID: String? = null
|
||||
|
||||
/**
|
||||
* An array of key agreement protocols that Alice’s client understands.
|
||||
|
@ -49,13 +55,13 @@ class KeyVerificationStart : SendToDeviceObject {
|
|||
* Other methods may be defined in the future
|
||||
*/
|
||||
@Json(name = "key_agreement_protocols")
|
||||
var keyAgreementProtocols: List<String>? = null
|
||||
override var keyAgreementProtocols: List<String>? = null
|
||||
|
||||
/**
|
||||
* An array of hashes that Alice’s client understands.
|
||||
* 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.
|
||||
|
@ -63,7 +69,7 @@ class KeyVerificationStart : SendToDeviceObject {
|
|||
* Other methods may be defined in the future.
|
||||
*/
|
||||
@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.
|
||||
|
@ -72,13 +78,13 @@ class KeyVerificationStart : SendToDeviceObject {
|
|||
* Other methods may be defined in the future
|
||||
*/
|
||||
@Json(name = "short_authentication_string")
|
||||
var shortAuthenticationStrings: List<String>? = null
|
||||
override var shortAuthenticationStrings: List<String>? = null
|
||||
|
||||
companion object {
|
||||
const val VERIF_METHOD_SAS = "m.sas.v1"
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| fromDevice.isNullOrBlank()
|
||||
|| method != VERIF_METHOD_SAS
|
||||
|
@ -95,4 +101,6 @@ class KeyVerificationStart : SendToDeviceObject {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
import android.util.Base64
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
|
@ -23,33 +24,20 @@ import im.vector.matrix.android.api.session.crypto.sas.SasMode
|
|||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
internal class IncomingSASVerificationTransaction(
|
||||
private val sasVerificationService: DefaultSasVerificationService,
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val credentials: Credentials,
|
||||
internal class DefaultIncomingSASVerificationTransaction(
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
override val credentials: Credentials,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserID: String)
|
||||
: SASVerificationTransaction(
|
||||
sasVerificationService,
|
||||
otherUserID: String
|
||||
) : SASVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
cryptoStore,
|
||||
sendToDeviceTask,
|
||||
taskExecutor,
|
||||
deviceFingerprint,
|
||||
transactionId,
|
||||
otherUserID,
|
||||
|
@ -78,10 +66,10 @@ internal class IncomingSASVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVerificationStart(startReq: KeyVerificationStart) {
|
||||
Timber.v("## SAS received verification request from state $state")
|
||||
override fun onVerificationStart(startReq: VerifInfoStart) {
|
||||
Timber.v("## SAS I: received verification request from state $state")
|
||||
if (state != SasVerificationTxState.None) {
|
||||
Timber.e("## received verification request from invalid state")
|
||||
Timber.e("## SAS I: received verification request from invalid state")
|
||||
// should I cancel??
|
||||
throw IllegalStateException("Interactive Key verification already started")
|
||||
}
|
||||
|
@ -92,7 +80,7 @@ internal class IncomingSASVerificationTransaction(
|
|||
|
||||
override fun performAccept() {
|
||||
if (state != SasVerificationTxState.OnStarted) {
|
||||
Timber.e("## Cannot perform accept from state $state")
|
||||
Timber.e("## SAS Cannot perform accept from state $state")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -109,7 +97,7 @@ internal class IncomingSASVerificationTransaction(
|
|||
if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() }
|
||||
|| agreedShortCode.isNullOrEmpty()) {
|
||||
// Failed to find agreement
|
||||
Timber.e("## Failed to find agreement ")
|
||||
Timber.e("## SAS Failed to find agreement ")
|
||||
cancel(CancelCode.UnknownMethod)
|
||||
return
|
||||
}
|
||||
|
@ -118,7 +106,7 @@ internal class IncomingSASVerificationTransaction(
|
|||
val mxDeviceInfo = cryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId)
|
||||
|
||||
if (mxDeviceInfo?.fingerprint() == null) {
|
||||
Timber.e("## Failed to find device key ")
|
||||
Timber.e("## SAS Failed to find device key ")
|
||||
// TODO force download keys!!
|
||||
// would be probably better to download the keys
|
||||
// for now I cancel
|
||||
|
@ -126,7 +114,7 @@ internal class IncomingSASVerificationTransaction(
|
|||
} else {
|
||||
// val otherKey = info.identityKey()
|
||||
// need to jump back to correct thread
|
||||
val accept = KeyVerificationAccept.create(
|
||||
val accept = transport.createAccept(
|
||||
tid = transactionId,
|
||||
keyAgreementProtocol = agreedProtocol!!,
|
||||
hash = agreedHash!!,
|
||||
|
@ -138,13 +126,13 @@ internal class IncomingSASVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
private fun doAccept(accept: KeyVerificationAccept) {
|
||||
private fun doAccept(accept: VerifInfoAccept) {
|
||||
this.accepted = accept
|
||||
Timber.v("## SAS accept request id:$transactionId")
|
||||
Timber.v("## SAS incoming accept request id:$transactionId")
|
||||
|
||||
// The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB,
|
||||
// concatenated with the canonical JSON representation of the content of the m.key.verification.start message
|
||||
val concat = getSAS().publicKey + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
|
||||
val concat = getSAS().publicKey + startReq!!.toCanonicalJson()
|
||||
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||
// we need to send this to other device now
|
||||
state = SasVerificationTxState.SendingAccept
|
||||
|
@ -156,15 +144,15 @@ internal class IncomingSASVerificationTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVerificationAccept(accept: KeyVerificationAccept) {
|
||||
override fun onVerificationAccept(accept: VerifInfoAccept) {
|
||||
Timber.v("## SAS invalid message for incoming request id:$transactionId")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) {
|
||||
override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) {
|
||||
Timber.v("## SAS received key for request id:$transactionId")
|
||||
if (state != SasVerificationTxState.SendingAccept && state != SasVerificationTxState.Accepted) {
|
||||
Timber.e("## received key from invalid state $state")
|
||||
Timber.e("## SAS received key from invalid state $state")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
|
@ -175,7 +163,7 @@ internal class IncomingSASVerificationTransaction(
|
|||
// sending Bob’s public key QB
|
||||
val pubKey = getSAS().publicKey
|
||||
|
||||
val keyToDevice = KeyVerificationKey.create(transactionId, pubKey)
|
||||
val keyToDevice = transport.createKey(transactionId, pubKey)
|
||||
// we need to send this to other device now
|
||||
state = SasVerificationTxState.SendingKey
|
||||
this.sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) {
|
||||
|
@ -206,14 +194,16 @@ internal class IncomingSASVerificationTransaction(
|
|||
// emoji: generate six bytes by using HKDF.
|
||||
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
|
||||
|
||||
Timber.e("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
|
||||
Timber.e("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}")
|
||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
|
||||
Timber.v("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}")
|
||||
}
|
||||
|
||||
state = SasVerificationTxState.ShortCodeReady
|
||||
}
|
||||
|
||||
override fun onKeyVerificationMac(vKey: KeyVerificationMac) {
|
||||
Timber.v("## SAS received mac for request id:$transactionId")
|
||||
override fun onKeyVerificationMac(vKey: VerifInfoMac) {
|
||||
Timber.v("## SAS I: received mac for request id:$transactionId")
|
||||
// Check for state?
|
||||
if (state != SasVerificationTxState.SendingKey
|
||||
&& state != SasVerificationTxState.KeySent
|
||||
|
@ -221,7 +211,7 @@ internal class IncomingSASVerificationTransaction(
|
|||
&& state != SasVerificationTxState.ShortCodeAccepted
|
||||
&& state != SasVerificationTxState.SendingMac
|
||||
&& state != SasVerificationTxState.MacSent) {
|
||||
Timber.e("## received key from invalid state $state")
|
||||
Timber.e("## SAS I: received key from invalid state $state")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
|
@ -21,34 +21,22 @@ import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRe
|
|||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import timber.log.Timber
|
||||
|
||||
internal class OutgoingSASVerificationRequest(
|
||||
private val sasVerificationService: DefaultSasVerificationService,
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val credentials: Credentials,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
internal class DefaultOutgoingSASVerificationRequest(
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
credentials: Credentials,
|
||||
cryptoStore: IMXCryptoStore,
|
||||
deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String)
|
||||
: SASVerificationTransaction(
|
||||
sasVerificationService,
|
||||
otherDeviceId: String
|
||||
) : SASVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
cryptoStore,
|
||||
sendToDeviceTask,
|
||||
taskExecutor,
|
||||
deviceFingerprint,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
|
@ -78,14 +66,14 @@ internal class OutgoingSASVerificationRequest(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVerificationStart(startReq: KeyVerificationStart) {
|
||||
Timber.e("## onVerificationStart - unexpected id:$transactionId")
|
||||
override fun onVerificationStart(startReq: VerifInfoStart) {
|
||||
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (state != SasVerificationTxState.None) {
|
||||
Timber.e("## start verification from invalid state")
|
||||
Timber.e("## SAS O: start verification from invalid state")
|
||||
// should I cancel??
|
||||
throw IllegalStateException("Interactive Key verification already started")
|
||||
}
|
||||
|
@ -111,10 +99,33 @@ internal class OutgoingSASVerificationRequest(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onVerificationAccept(accept: KeyVerificationAccept) {
|
||||
Timber.v("## onVerificationAccept id:$transactionId")
|
||||
// fun request() {
|
||||
// if (state != SasVerificationTxState.None) {
|
||||
// Timber.e("## start verification from invalid state")
|
||||
// // should I cancel??
|
||||
// throw IllegalStateException("Interactive Key verification already started")
|
||||
// }
|
||||
//
|
||||
// val requestMessage = KeyVerificationRequest(
|
||||
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||
// timestamp = System.currentTimeMillis().toInt(),
|
||||
// transactionID = transactionId
|
||||
// )
|
||||
//
|
||||
// sendToOther(
|
||||
// EventType.KEY_VERIFICATION_REQUEST,
|
||||
// requestMessage,
|
||||
// SasVerificationTxState.None,
|
||||
// CancelCode.User,
|
||||
// null
|
||||
// )
|
||||
// }
|
||||
|
||||
override fun onVerificationAccept(accept: VerifInfoAccept) {
|
||||
Timber.v("## SAS O: onVerificationAccept id:$transactionId")
|
||||
if (state != SasVerificationTxState.Started) {
|
||||
Timber.e("## received accept request from invalid state $state")
|
||||
Timber.e("## SAS O: received accept request from invalid state $state")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
|
@ -123,7 +134,7 @@ internal class OutgoingSASVerificationRequest(
|
|||
|| !KNOWN_HASHES.contains(accept.hash)
|
||||
|| !KNOWN_MACS.contains(accept.messageAuthenticationCode)
|
||||
|| accept.shortAuthenticationStrings!!.intersect(KNOWN_SHORT_CODES).isEmpty()) {
|
||||
Timber.e("## received accept request from invalid state")
|
||||
Timber.e("## SAS O: received accept request from invalid state")
|
||||
cancel(CancelCode.UnknownMethod)
|
||||
return
|
||||
}
|
||||
|
@ -137,7 +148,7 @@ internal class OutgoingSASVerificationRequest(
|
|||
// and replies with a to_device message with type set to “m.key.verification.key”, sending Alice’s public key QA
|
||||
val pubKey = getSAS().publicKey
|
||||
|
||||
val keyToDevice = KeyVerificationKey.create(transactionId, pubKey)
|
||||
val keyToDevice = transport.createKey(transactionId, pubKey)
|
||||
// we need to send this to other device now
|
||||
state = SasVerificationTxState.SendingKey
|
||||
sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) {
|
||||
|
@ -148,8 +159,8 @@ internal class OutgoingSASVerificationRequest(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) {
|
||||
Timber.v("## onKeyVerificationKey id:$transactionId")
|
||||
override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) {
|
||||
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
|
||||
if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent) {
|
||||
Timber.e("## received key from invalid state $state")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
|
@ -163,7 +174,7 @@ internal class OutgoingSASVerificationRequest(
|
|||
// in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
||||
|
||||
// check commitment
|
||||
val concat = vKey.key + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
|
||||
val concat = vKey.key + startReq!!.toCanonicalJson()
|
||||
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
|
||||
|
||||
if (accepted!!.commitment.equals(otherCommitment)) {
|
||||
|
@ -190,14 +201,14 @@ internal class OutgoingSASVerificationRequest(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onKeyVerificationMac(vKey: KeyVerificationMac) {
|
||||
Timber.v("## onKeyVerificationMac id:$transactionId")
|
||||
override fun onKeyVerificationMac(vKey: VerifInfoMac) {
|
||||
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
||||
if (state != SasVerificationTxState.OnKeyReceived
|
||||
&& state != SasVerificationTxState.ShortCodeReady
|
||||
&& state != SasVerificationTxState.ShortCodeAccepted
|
||||
&& state != SasVerificationTxState.SendingMac
|
||||
&& state != SasVerificationTxState.MacSent) {
|
||||
Timber.e("## received key from invalid state $state")
|
||||
Timber.e("## SAS O: received key from invalid state $state")
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
|
@ -21,6 +21,7 @@ import android.os.Looper
|
|||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
||||
|
@ -28,6 +29,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.*
|
||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
|
@ -35,24 +37,23 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.RequestVerificationDMTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||
import im.vector.matrix.android.internal.task.TaskConstraints
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.lang.Exception
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Manages all current verifications transactions with short codes.
|
||||
* Short codes interactive verification is a more user friendly way of verifying devices
|
||||
* that is still maintaining a good level of security (alternative to the 43-character strings compare method).
|
||||
*/
|
||||
import kotlin.collections.set
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials,
|
||||
|
@ -61,12 +62,18 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
private val deviceListManager: DeviceListManager,
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val requestVerificationDMTask: DefaultRequestVerificationDMTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory,
|
||||
private val sasToDeviceTransportFactory: SasToDeviceTransportFactory,
|
||||
private val taskExecutor: TaskExecutor)
|
||||
: VerificationTransaction.Listener, SasVerificationService {
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
// Cannot be injected in constructor as it creates a dependency cycle
|
||||
lateinit var cryptoService: CryptoService
|
||||
|
||||
// map [sender : [transaction]]
|
||||
private val txMap = HashMap<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>()
|
||||
|
||||
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) {
|
||||
Timber.e("## SAS received Start request ${event.eventId}")
|
||||
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
||||
Timber.v("## SAS received Start request $startReq")
|
||||
|
||||
val otherUserId = event.senderId
|
||||
if (!startReq.isValid()) {
|
||||
Timber.e("## received invalid verification request")
|
||||
Timber.e("## SAS received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
cancelTransaction(
|
||||
startReq.transactionID!!,
|
||||
// cancelTransaction(
|
||||
// startReq.transactionID!!,
|
||||
// otherUserId!!,
|
||||
// startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
// CancelCode.UnknownMethod
|
||||
// )
|
||||
sasToDeviceTransportFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
CancelCode.UnknownMethod
|
||||
|
@ -167,8 +256,22 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
return
|
||||
}
|
||||
// Download device keys prior to everything
|
||||
handleStart(otherUserId, startReq) {
|
||||
it.transport = sasToDeviceTransportFactory.createTransport(it)
|
||||
}?.let {
|
||||
sasToDeviceTransportFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleStart(otherUserId: String?, startReq: VerifInfoStart, txConfigure: (SASVerificationTransaction) -> Unit): CancelCode? {
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}")
|
||||
if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) {
|
||||
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
|
||||
Timber.v("## SAS onStartRequestReceived $startReq")
|
||||
val tid = startReq.transactionID!!
|
||||
val existing = getExistingTransaction(otherUserId, tid)
|
||||
val existingTxs = getExistingTransactionsForUser(otherUserId)
|
||||
|
@ -176,43 +279,46 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
// should cancel both!
|
||||
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
|
||||
existing.cancel(CancelCode.UnexpectedMessage)
|
||||
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
return CancelCode.UnexpectedMessage
|
||||
// cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
} else if (existingTxs?.isEmpty() == false) {
|
||||
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
|
||||
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
|
||||
existingTxs.forEach {
|
||||
it.cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
return CancelCode.UnexpectedMessage
|
||||
// cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
} else {
|
||||
// Ok we can create
|
||||
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||
val tx = IncomingSASVerificationTransaction(
|
||||
this,
|
||||
val tx = DefaultIncomingSASVerificationTransaction(
|
||||
// this,
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
cryptoStore,
|
||||
sendToDeviceTask,
|
||||
taskExecutor,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
startReq.transactionID!!,
|
||||
otherUserId)
|
||||
otherUserId).also { txConfigure(it) }
|
||||
addTransaction(tx)
|
||||
tx.acceptToDeviceEvent(otherUserId, startReq)
|
||||
tx.acceptVerificationEvent(otherUserId, startReq)
|
||||
} else {
|
||||
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
||||
cancelTransaction(tid, otherUserId, startReq.fromDevice
|
||||
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
|
||||
return CancelCode.UnknownMethod
|
||||
// cancelTransaction(tid, otherUserId, startReq.fromDevice
|
||||
// ?: event.getSenderKey()!!, CancelCode.UnknownMethod)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
return CancelCode.UnexpectedMessage
|
||||
// cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun checkKeysAreDownloaded(otherUserId: String,
|
||||
startReq: KeyVerificationStart): MXUsersDevicesMap<MXDeviceInfo>? {
|
||||
startReq: VerifInfoStart): MXUsersDevicesMap<MXDeviceInfo>? {
|
||||
return try {
|
||||
val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
|
||||
val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null
|
||||
|
@ -222,17 +328,36 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun onCancelReceived(event: Event) {
|
||||
private fun onRoomCancelReceived(event: Event) {
|
||||
val cancelReq = event.getClearContent().toModel<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")
|
||||
val cancelReq = event.getClearContent().toModel<KeyVerificationCancel>()!!
|
||||
|
||||
if (!cancelReq.isValid()) {
|
||||
// ignore
|
||||
Timber.e("## Received invalid accept request")
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
val otherUserId = event.senderId!!
|
||||
|
||||
handleOnCancel(otherUserId, cancelReq)
|
||||
}
|
||||
|
||||
private fun handleOnCancel(otherUserId: String, cancelReq: VerifInfoCancel) {
|
||||
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
||||
val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
||||
if (existing == null) {
|
||||
|
@ -245,65 +370,119 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun onAcceptReceived(event: Event) {
|
||||
val acceptReq = event.getClearContent().toModel<KeyVerificationAccept>()!!
|
||||
private fun onRoomAcceptReceived(event: Event) {
|
||||
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()) {
|
||||
// ignore
|
||||
Timber.e("## Received invalid accept request")
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
val otherUserId = event.senderId!!
|
||||
val otherUserId = senderId
|
||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
||||
if (existing == null) {
|
||||
Timber.e("## Received invalid accept request")
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
|
||||
if (existing is SASVerificationTransaction) {
|
||||
existing.acceptToDeviceEvent(otherUserId, acceptReq)
|
||||
existing.acceptVerificationEvent(otherUserId, acceptReq)
|
||||
} else {
|
||||
// not other types now
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onKeyReceived(event: Event) {
|
||||
private fun onRoomKeyRequestReceived(event: Event) {
|
||||
val keyReq = event.getClearContent().toModel<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>()!!
|
||||
|
||||
if (!keyReq.isValid()) {
|
||||
// ignore
|
||||
Timber.e("## Received invalid key request")
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
return
|
||||
}
|
||||
handleKeyReceived(event, keyReq)
|
||||
}
|
||||
|
||||
private fun handleKeyReceived(event: Event, keyReq: VerifInfoKey) {
|
||||
Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq")
|
||||
val otherUserId = event.senderId!!
|
||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
||||
if (existing == null) {
|
||||
Timber.e("## Received invalid accept request")
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
if (existing is SASVerificationTransaction) {
|
||||
existing.acceptToDeviceEvent(otherUserId, keyReq)
|
||||
existing.acceptVerificationEvent(otherUserId, keyReq)
|
||||
} else {
|
||||
// not other types now
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onMacReceived(event: Event) {
|
||||
val macReq = event.getClearContent().toModel<KeyVerificationMac>()!!
|
||||
|
||||
if (!macReq.isValid()) {
|
||||
private fun onRoomMacReceived(event: Event) {
|
||||
val macReq = event.getClearContent().toModel<MessageVerificationMacContent>()
|
||||
?.copy(
|
||||
// relates_to is in clear in encrypted payload
|
||||
relatesTo = event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
)
|
||||
if (macReq == null || macReq.isValid().not() || event.senderId == null) {
|
||||
// ignore
|
||||
Timber.e("## Received invalid key request")
|
||||
Timber.e("## SAS Received invalid mac request")
|
||||
// TODO should we cancel?
|
||||
return
|
||||
}
|
||||
val otherUserId = event.senderId!!
|
||||
val existing = getExistingTransaction(otherUserId, macReq.transactionID!!)
|
||||
handleMacReceived(event.senderId, macReq)
|
||||
}
|
||||
|
||||
private fun onMacReceived(event: Event) {
|
||||
val macReq = event.getClearContent().toModel<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) {
|
||||
Timber.e("## Received invalid accept request")
|
||||
Timber.e("## SAS Received invalid accept request")
|
||||
return
|
||||
}
|
||||
if (existing is SASVerificationTransaction) {
|
||||
existing.acceptToDeviceEvent(otherUserId, macReq)
|
||||
existing.acceptVerificationEvent(senderId, macReq)
|
||||
} else {
|
||||
// not other types known for now
|
||||
}
|
||||
|
@ -346,13 +525,10 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
val txID = createUniqueIDForTransaction(userId, deviceID)
|
||||
// should check if already one (and cancel it)
|
||||
if (KeyVerificationStart.VERIF_METHOD_SAS == method) {
|
||||
val tx = OutgoingSASVerificationRequest(
|
||||
this,
|
||||
val tx = DefaultOutgoingSASVerificationRequest(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
cryptoStore,
|
||||
sendToDeviceTask,
|
||||
taskExecutor,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
txID,
|
||||
userId,
|
||||
|
@ -366,6 +542,30 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
}
|
||||
}
|
||||
|
||||
override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<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
|
||||
*/
|
||||
|
@ -390,24 +590,28 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||
this.removeTransaction(tx.otherUserId, tx.transactionId)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) {
|
||||
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
||||
val contentMap = MXUsersDevicesMap<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)
|
||||
}
|
||||
//
|
||||
// fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode, roomId: String? = null) {
|
||||
// val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
||||
// val contentMap = MXUsersDevicesMap<Any>()
|
||||
// contentMap.setObject(userId, userDevice, cancelMessage)
|
||||
//
|
||||
// if (roomId != null) {
|
||||
//
|
||||
// } else {
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import android.os.Build
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
|
||||
|
@ -26,13 +25,8 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import org.matrix.olm.OlmSAS
|
||||
import org.matrix.olm.OlmUtility
|
||||
import timber.log.Timber
|
||||
|
@ -42,12 +36,9 @@ import kotlin.properties.Delegates
|
|||
* Represents an ongoing short code interactive key verification between two devices.
|
||||
*/
|
||||
internal abstract class SASVerificationTransaction(
|
||||
private val sasVerificationService: DefaultSasVerificationService,
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val credentials: Credentials,
|
||||
open val credentials: Credentials,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserId: String,
|
||||
|
@ -55,6 +46,8 @@ internal abstract class SASVerificationTransaction(
|
|||
isIncoming: Boolean) :
|
||||
VerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming) {
|
||||
|
||||
lateinit var transport: SasTransport
|
||||
|
||||
companion object {
|
||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
|
||||
|
@ -95,13 +88,13 @@ internal abstract class SASVerificationTransaction(
|
|||
|
||||
private var olmSas: OlmSAS? = null
|
||||
|
||||
var startReq: KeyVerificationStart? = null
|
||||
var accepted: KeyVerificationAccept? = null
|
||||
var startReq: VerifInfoStart? = null
|
||||
var accepted: VerifInfoAccept? = null
|
||||
var otherKey: String? = null
|
||||
var shortCodeBytes: ByteArray? = null
|
||||
|
||||
var myMac: KeyVerificationMac? = null
|
||||
var theirMac: KeyVerificationMac? = null
|
||||
var myMac: VerifInfoMac? = null
|
||||
var theirMac: VerifInfoMac? = null
|
||||
|
||||
fun getSAS(): OlmSAS {
|
||||
if (olmSas == null) olmSas = OlmSAS()
|
||||
|
@ -160,7 +153,7 @@ internal abstract class SASVerificationTransaction(
|
|||
return
|
||||
}
|
||||
|
||||
val macMsg = KeyVerificationMac.create(transactionId, mapOf(keyId to macString), keyStrings)
|
||||
val macMsg = transport.createMac(transactionId, mapOf(keyId to macString), keyStrings)
|
||||
myMac = macMsg
|
||||
state = SasVerificationTxState.SendingMac
|
||||
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, SasVerificationTxState.MacSent, CancelCode.User) {
|
||||
|
@ -176,25 +169,25 @@ internal abstract class SASVerificationTransaction(
|
|||
} // if not wait for it
|
||||
}
|
||||
|
||||
override fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject) {
|
||||
when (event) {
|
||||
is KeyVerificationStart -> onVerificationStart(event)
|
||||
is KeyVerificationAccept -> onVerificationAccept(event)
|
||||
is KeyVerificationKey -> onKeyVerificationKey(senderId, event)
|
||||
is KeyVerificationMac -> onKeyVerificationMac(event)
|
||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||
when (info) {
|
||||
is VerifInfoStart -> onVerificationStart(info)
|
||||
is VerifInfoAccept -> onVerificationAccept(info)
|
||||
is VerifInfoKey -> onKeyVerificationKey(senderId, info)
|
||||
is VerifInfoMac -> onKeyVerificationMac(info)
|
||||
else -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onVerificationStart(startReq: KeyVerificationStart)
|
||||
abstract fun onVerificationStart(startReq: VerifInfoStart)
|
||||
|
||||
abstract fun onVerificationAccept(accept: KeyVerificationAccept)
|
||||
abstract fun onVerificationAccept(accept: VerifInfoAccept)
|
||||
|
||||
abstract fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey)
|
||||
abstract fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey)
|
||||
|
||||
abstract fun onKeyVerificationMac(vKey: KeyVerificationMac)
|
||||
abstract fun onKeyVerificationMac(vKey: VerifInfoMac)
|
||||
|
||||
protected fun verifyMacs() {
|
||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
||||
|
@ -245,7 +238,7 @@ internal abstract class SASVerificationTransaction(
|
|||
// if none of the keys could be verified, then error because the app
|
||||
// should be informed about that
|
||||
if (verifiedDevices.isEmpty()) {
|
||||
Timber.e("Verification: No devices verified")
|
||||
Timber.e("## SAS Verification: No devices verified")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
}
|
||||
|
@ -254,6 +247,7 @@ internal abstract class SASVerificationTransaction(
|
|||
verifiedDevices.forEach {
|
||||
setDeviceVerified(it, otherUserId)
|
||||
}
|
||||
transport.done(transactionId)
|
||||
state = SasVerificationTxState.Verified
|
||||
}
|
||||
|
||||
|
@ -270,41 +264,15 @@ internal abstract class SASVerificationTransaction(
|
|||
override fun cancel(code: CancelCode) {
|
||||
cancelledReason = code
|
||||
state = SasVerificationTxState.Cancelled
|
||||
sasVerificationService.cancelTransaction(
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId ?: "",
|
||||
code)
|
||||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
||||
}
|
||||
|
||||
protected fun sendToOther(type: String,
|
||||
keyToDevice: Any,
|
||||
keyToDevice: VerificationInfo,
|
||||
nextState: SasVerificationTxState,
|
||||
onErrorReason: CancelCode,
|
||||
onDone: (() -> Unit)?) {
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
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)
|
||||
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
|
||||
}
|
||||
|
||||
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.SasVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject
|
||||
|
||||
/**
|
||||
* Generic interactive key verification transaction
|
||||
|
@ -42,7 +41,7 @@ internal abstract class VerificationTransaction(
|
|||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
abstract fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject)
|
||||
abstract fun acceptVerificationEvent(senderId: String, info: VerificationInfo)
|
||||
|
||||
abstract fun cancel(code: CancelCode)
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
|
|||
|
||||
return Failure.ServerError(matrixError, httpCode)
|
||||
}
|
||||
} catch (ex: JsonDataException) {
|
||||
} catch (ex: Exception) {
|
||||
// This is not a MatrixError
|
||||
Timber.w("The error returned by the server is not a MatrixError")
|
||||
} catch (ex: JsonEncodingException) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.InitialSyncProgressService
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver
|
||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
||||
import im.vector.matrix.android.internal.di.*
|
||||
|
@ -164,6 +165,10 @@ internal abstract class SessionModule {
|
|||
@IntoSet
|
||||
abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindVerificationEventObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService
|
||||
|
||||
|
|
|
@ -157,7 +157,8 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
|
|||
|
||||
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
||||
monarchy.writeAsync { realm ->
|
||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.let {
|
||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId
|
||||
?: "").findFirst()?.let {
|
||||
it.deleteFromRealm()
|
||||
}
|
||||
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let {
|
||||
|
|
|
@ -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 {
|
||||
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.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -28,7 +27,7 @@ internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarc
|
|||
|
||||
suspend fun updateSendState(eventId: String, sendState: SendState) {
|
||||
Timber.v("Update local state of $eventId to ${sendState.name}")
|
||||
monarchy.awaitTransaction { realm ->
|
||||
monarchy.writeAsync { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||
|
|
|
@ -17,4 +17,7 @@
|
|||
<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="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>
|
|
@ -207,6 +207,11 @@ interface FragmentModule {
|
|||
@FragmentKey(VectorSettingsNotificationPreferenceFragment::class)
|
||||
fun bindVectorSettingsNotificationPreferenceFragment(fragment: VectorSettingsNotificationPreferenceFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(VectorSettingsLabsFragment::class)
|
||||
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(VectorSettingsPreferencesFragment::class)
|
||||
|
|
|
@ -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),
|
||||
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
|
||||
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
|
||||
get() = command.length + 1
|
||||
|
|
|
@ -244,6 +244,17 @@ object CommandParser {
|
|||
|
||||
ParsedCommand.SendSpoiler(message)
|
||||
}
|
||||
Command.SHRUG.command -> {
|
||||
val message = textMessage.subSequence(Command.SHRUG.command.length, textMessage.length).trim()
|
||||
|
||||
ParsedCommand.SendShrug(message)
|
||||
}
|
||||
|
||||
Command.VERIFY_USER.command -> {
|
||||
val message = textMessage.substring(Command.VERIFY_USER.command.length).trim()
|
||||
|
||||
ParsedCommand.VerifyUser(message)
|
||||
}
|
||||
else -> {
|
||||
// Unknown command
|
||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||
|
|
|
@ -46,4 +46,6 @@ sealed class ParsedCommand {
|
|||
class SetMarkdown(val enable: Boolean) : ParsedCommand()
|
||||
object ClearScalarToken : ParsedCommand()
|
||||
class SendSpoiler(val message: String) : ParsedCommand()
|
||||
class SendShrug(val message: CharSequence) : ParsedCommand()
|
||||
class VerifyUser(val userId: String) : ParsedCommand()
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
|||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.BuildConfig
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
@ -377,6 +376,25 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendShrug -> {
|
||||
val sequence: CharSequence = buildString {
|
||||
append("¯\\_(ツ)_/¯")
|
||||
.apply {
|
||||
if (slashCommandResult.message.isNotEmpty()) {
|
||||
append(" ")
|
||||
append(slashCommandResult.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
room.sendTextMessage(sequence)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.VerifyUser -> {
|
||||
session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId, null)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.ChangeTopic -> {
|
||||
handleChangeTopicSlashCommand(slashCommandResult)
|
||||
popDraft()
|
||||
|
|
|
@ -64,6 +64,16 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||
encryptedItemFactory.create(event, nextEvent, highlight, callback)
|
||||
}
|
||||
}
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC -> {
|
||||
// These events are filtered from timeline in normal case
|
||||
// Only visible in developer mode
|
||||
defaultItemFactory.create(event, highlight, readMarkerVisible, callback)
|
||||
}
|
||||
|
||||
// Unhandled event types (yet)
|
||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback)
|
||||
|
|
|
@ -42,7 +42,13 @@ object TimelineDisplayableEvents {
|
|||
|
||||
val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf(
|
||||
EventType.REDACTION,
|
||||
EventType.REACTION
|
||||
EventType.REACTION,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_MAC,
|
||||
EventType.KEY_VERIFICATION_KEY
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.net.Uri
|
|||
import android.provider.MediaStore
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.riotx.BuildConfig
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
|
@ -256,7 +257,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
}
|
||||
|
||||
fun labAllowedExtendedLogging(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, BuildConfig.DEBUG)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,14 +17,21 @@
|
|||
package im.vector.riotx.features.settings
|
||||
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.preference.VectorSwitchPreference
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsLabsFragment : VectorSettingsBaseFragment() {
|
||||
class VectorSettingsLabsFragment @Inject constructor(val vectorPreferences: VectorPreferences) : VectorSettingsBaseFragment() {
|
||||
|
||||
override var titleRes = R.string.room_settings_labs_pref_title
|
||||
override val preferenceXmlRes = R.xml.vector_settings_labs
|
||||
|
||||
override fun bindPref() {
|
||||
// Lab
|
||||
|
||||
findPreference<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 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="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>
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<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="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="reaction_search_type_hint">Type keywords to find a reaction.</string>
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
||||
android:title="@string/labs_swipe_to_reply_in_timeline" />
|
||||
|
||||
|
||||
<im.vector.riotx.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
||||
|
|
Loading…
Reference in New Issue