commit
9a79297e14
|
@ -51,8 +51,8 @@ class SASTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
val bobTxCreatedLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
|
@ -64,18 +64,18 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val txID = aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, bobSession.getMyDevice().deviceId)
|
||||
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, bobSession.getMyDevice().deviceId)
|
||||
assertNotNull("Alice should have a started transaction", txID)
|
||||
|
||||
val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
||||
|
||||
mTestHelper.await(bobTxCreatedLatch)
|
||||
bobSasMgr.removeListener(bobListener)
|
||||
bobVerificationService.removeListener(bobListener)
|
||||
|
||||
val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
|
||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
||||
|
@ -105,7 +105,7 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener2)
|
||||
bobVerificationService.addListener(bobListener2)
|
||||
|
||||
aliceSasTx.cancel(CancelCode.User)
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
@ -120,8 +120,8 @@ class SASTest : InstrumentedTest {
|
|||
assertEquals("Should be User cancelled on bob side",
|
||||
CancelCode.User, aliceSasTx.cancelledReason)
|
||||
|
||||
assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID))
|
||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSession.getSasVerificationService().addListener(bobListener)
|
||||
bobSession.getVerificationService().addListener(bobListener)
|
||||
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
|
@ -179,7 +179,7 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSession.getSasVerificationService().addListener(aliceListener)
|
||||
aliceSession.getVerificationService().addListener(aliceListener)
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
||||
|
||||
|
@ -303,7 +303,7 @@ class SASTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
|
||||
val aliceCreatedLatch = CountDownLatch(2)
|
||||
val aliceCancelledLatch = CountDownLatch(2)
|
||||
|
@ -322,12 +322,12 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobUserId = bobSession!!.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
|
||||
mTestHelper.await(aliceCreatedLatch)
|
||||
mTestHelper.await(aliceCancelledLatch)
|
||||
|
@ -345,8 +345,8 @@ class SASTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
var accepted: KeyVerificationAccept? = null
|
||||
var startReq: KeyVerificationStart? = null
|
||||
|
@ -366,7 +366,7 @@ class SASTest : InstrumentedTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
@ -380,11 +380,11 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
mTestHelper.await(aliceAcceptedLatch)
|
||||
|
||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||
|
@ -409,26 +409,26 @@ class SASTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationRequest).uxState
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
|
@ -449,16 +449,16 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
val verificationSAS = aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
mTestHelper.await(aliceSASLatch)
|
||||
mTestHelper.await(bobSASLatch)
|
||||
|
||||
val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||
val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||
|
||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
||||
|
@ -473,29 +473,29 @@ class SASTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
val aliceVerificationService = aliceSession.getVerificationService()
|
||||
val bobVerificationService = bobSession!!.getVerificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.VerificationListener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationRequest).uxState
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.VerificationListener {
|
||||
|
@ -519,11 +519,11 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
|
||||
mTestHelper.await(aliceSASLatch)
|
||||
mTestHelper.await(bobSASLatch)
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ interface CryptoService {
|
|||
|
||||
fun isCryptoEnabled(): Boolean
|
||||
|
||||
fun getSasVerificationService(): VerificationService
|
||||
fun getVerificationService(): VerificationService
|
||||
|
||||
fun getCrossSigningService(): CrossSigningService
|
||||
|
||||
|
@ -118,8 +118,9 @@ interface CryptoService {
|
|||
|
||||
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
|
||||
|
||||
fun getCryptoDeviceInfo(userId: String) : List<CryptoDeviceInfo>
|
||||
fun getLiveCryptoDeviceInfo(userId: String) : LiveData<List<CryptoDeviceInfo>>
|
||||
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
||||
|
||||
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
|
||||
|
||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||
|
||||
|
|
|
@ -26,33 +26,41 @@ import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
|||
|
||||
interface CrossSigningService {
|
||||
|
||||
fun isUserTrusted(userId: String) : Boolean
|
||||
fun isCrossSigningEnabled(): Boolean
|
||||
|
||||
fun isUserTrusted(otherUserId: String): Boolean
|
||||
|
||||
/**
|
||||
* Will not force a download of the key, but will verify signatures trust chain.
|
||||
* Checks that my trusted user key has signed the other user UserKey
|
||||
*/
|
||||
fun checkUserTrust(userId: String) : UserTrustResult
|
||||
fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||
|
||||
/**
|
||||
* Initialize cross signing for this user.
|
||||
* Users needs to enter credentials
|
||||
*/
|
||||
fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>? = null)
|
||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||
callback: MatrixCallback<Unit>? = null)
|
||||
|
||||
fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo?
|
||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||
|
||||
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
||||
|
||||
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||
|
||||
fun canCrossSign(): Boolean
|
||||
|
||||
fun trustUser(userId: String, callback: MatrixCallback<Unit>)
|
||||
fun trustUser(otherUserId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Sign one of your devices and upload the signature
|
||||
*/
|
||||
fun signDevice(deviceId: String, callback: MatrixCallback<Unit>)
|
||||
fun signDevice(deviceId: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
fun checkDeviceTrust(userId: String, deviceId: String, locallyTrusted: Boolean?) : DeviceTrustResult
|
||||
fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO Rename package
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
enum class CancelCode(val value: String, val humanReadable: String) {
|
||||
|
@ -25,7 +27,9 @@ enum class CancelCode(val value: String, val humanReadable: String) {
|
|||
UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"),
|
||||
InvalidMessage("m.invalid_message", "an invalid message was received"),
|
||||
MismatchedKeys("m.key_mismatch", "Key mismatch"),
|
||||
UserMismatchError("m.user_error", "User mismatch")
|
||||
UserError("m.user_error", "User error"),
|
||||
MismatchedUser("m.user_mismatch", "User mismatch"),
|
||||
QrCodeInvalid("m.qr_code.invalid", "Invalid QR code")
|
||||
}
|
||||
|
||||
fun safeValueOf(code: String?): CancelCode {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface OutgoingSasVerificationRequest : SasVerificationTransaction {
|
||||
interface OutgoingSasVerificationTransaction : SasVerificationTransaction {
|
||||
val uxState: UxState
|
||||
|
||||
enum class UxState {
|
|
@ -1,7 +0,0 @@
|
|||
package im.vector.matrix.android.api.session.crypto.sas
|
||||
|
||||
interface QRVerificationTransaction : VerificationTransaction {
|
||||
|
||||
fun userHasScannedRemoteQrCode(scannedData: String)
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto.sas
|
||||
|
||||
interface QrCodeVerificationTransaction : VerificationTransaction {
|
||||
|
||||
/**
|
||||
* To use to display a qr code, for the other user to scan it
|
||||
*/
|
||||
val qrCodeText: String?
|
||||
|
||||
/**
|
||||
* Call when you have scan the other user QR code
|
||||
*/
|
||||
fun userHasScannedOtherQrCode(otherQrCodeText: String)
|
||||
}
|
|
@ -24,8 +24,8 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification
|
|||
* 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,
|
||||
* Verification is a user-friendly key verification process.
|
||||
* 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 VerificationService {
|
||||
|
@ -39,23 +39,24 @@ interface VerificationService {
|
|||
*/
|
||||
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
|
||||
|
||||
fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction?
|
||||
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
|
||||
|
||||
fun getExistingVerificationRequest(otherUser: String): List<PendingVerificationRequest>?
|
||||
fun getExistingVerificationRequest(otherUserId: String): List<PendingVerificationRequest>?
|
||||
|
||||
fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest?
|
||||
fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest?
|
||||
|
||||
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
|
||||
|
||||
fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String?
|
||||
fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceID: String): String?
|
||||
|
||||
/**
|
||||
* Request a key verification from another user using toDevice events.
|
||||
*/
|
||||
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String, localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, otherUserId: String, roomId: String, localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||
|
||||
fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String)
|
||||
|
||||
// Only SAS method is supported for the moment
|
||||
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
||||
transactionId: String,
|
||||
roomId: String,
|
||||
|
@ -66,7 +67,10 @@ interface VerificationService {
|
|||
/**
|
||||
* Returns false if the request is unknown
|
||||
*/
|
||||
fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean
|
||||
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
transactionId: String): Boolean
|
||||
|
||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||
|
||||
|
|
|
@ -1,19 +1,37 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto.sas
|
||||
|
||||
interface VerificationTransaction {
|
||||
|
||||
|
||||
var state: VerificationTxState
|
||||
|
||||
val cancelledReason: CancelCode?
|
||||
val transactionId: String
|
||||
val otherUserId: String
|
||||
var otherDeviceId: String?
|
||||
|
||||
// TODO Not used. Remove?
|
||||
val isIncoming: Boolean
|
||||
/**
|
||||
* User wants to cancel the transaction
|
||||
*/
|
||||
fun cancel()
|
||||
|
||||
fun cancel(code: CancelCode)
|
||||
|
||||
fun isToDeviceTransport(): Boolean
|
||||
|
|
|
@ -44,6 +44,8 @@ enum class VerificationTxState {
|
|||
Verified,
|
||||
|
||||
// Global: The verification has been cancelled (by me or other), see cancelReason for details
|
||||
// When I do the cancel
|
||||
Cancelled,
|
||||
// When the other user do a cancel
|
||||
OnCancelled
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ 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.supportedVerificationMethods
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
|
@ -34,7 +35,8 @@ internal data class MessageVerificationStartContent(
|
|||
@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?
|
||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?,
|
||||
@Json(name = "secret") override val sharedSecret: String?
|
||||
) : VerificationInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
|
@ -44,22 +46,39 @@ internal data class MessageVerificationStartContent(
|
|||
override val transactionID: String?
|
||||
get() = relatesTo?.eventId
|
||||
|
||||
// TODO Move those method to the interface?
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| fromDevice.isNullOrBlank()
|
||||
|| method !in supportedVerificationMethods
|
||||
|| keyAgreementProtocols.isNullOrEmpty()
|
||||
|| hashes.isNullOrEmpty()
|
||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
||||
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidSas(): Boolean {
|
||||
if (keyAgreementProtocols.isNullOrEmpty()
|
||||
|| hashes.isNullOrEmpty()
|
||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidReciprocate(): Boolean {
|
||||
if (sharedSecret.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
||||
|
|
|
@ -30,3 +30,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
|
|||
* Matrix algorithm value for megolm keys backup.
|
||||
*/
|
||||
const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
|
||||
|
||||
// TODO Refacto: use this constants everywhere
|
||||
const val ed25519 = "ed25519"
|
||||
const val curve25519 = "curve25519"
|
||||
|
|
|
@ -126,8 +126,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
||||
//
|
||||
private val roomDecryptorProvider: RoomDecryptorProvider,
|
||||
// The SAS verification service.
|
||||
private val sasVerificationService: DefaultVerificationService,
|
||||
// The verification service.
|
||||
private val verificationService: DefaultVerificationService,
|
||||
|
||||
private val crossSigningService: DefaultCrossSigningService,
|
||||
//
|
||||
|
@ -157,7 +157,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
|
||||
init {
|
||||
sasVerificationService.cryptoService = this
|
||||
verificationService.cryptoService = this
|
||||
}
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
@ -343,7 +343,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
/**
|
||||
* @return the VerificationService
|
||||
*/
|
||||
override fun getSasVerificationService() = sasVerificationService
|
||||
override fun getVerificationService() = verificationService
|
||||
|
||||
override fun getCrossSigningService() = crossSigningService
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.crypto.crosssigning
|
|||
import androidx.lifecycle.LiveData
|
||||
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.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
@ -41,6 +40,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
|||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.olm.OlmPkSigning
|
||||
import org.matrix.olm.OlmUtility
|
||||
|
@ -50,7 +50,6 @@ import javax.inject.Inject
|
|||
@SessionScope
|
||||
internal class DefaultCrossSigningService @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
private val credentials: Credentials,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
|
||||
private val olmDevice: MXOlmDevice,
|
||||
|
@ -145,8 +144,6 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>?) {
|
||||
Timber.d("## CrossSigning initializeCrossSigning")
|
||||
|
||||
val myUserID = credentials.userId
|
||||
|
||||
// =================
|
||||
// MASTER KEY
|
||||
// =================
|
||||
|
@ -166,7 +163,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey")
|
||||
|
||||
// Sign userSigningKey with master
|
||||
val signedUSK = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.USER_SIGNING)
|
||||
val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING)
|
||||
.key(uskPublicKey)
|
||||
.build()
|
||||
.canonicalSignable()
|
||||
|
@ -182,23 +179,23 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
|
||||
|
||||
// Sign userSigningKey with master
|
||||
val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(myUserID, KeyUsage.SELF_SIGNING)
|
||||
val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
||||
.key(sskPublicKey)
|
||||
.build().signalableJSONDictionary()).let { masterPkOlm.sign(it) }
|
||||
|
||||
// I need to upload the keys
|
||||
val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.MASTER)
|
||||
val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER)
|
||||
.key(masterPublicKey)
|
||||
.build()
|
||||
val params = UploadSigningKeysTask.Params(
|
||||
masterKey = mskCrossSigningKeyInfo,
|
||||
userKey = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.USER_SIGNING)
|
||||
userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING)
|
||||
.key(uskPublicKey)
|
||||
.signature(myUserID, masterPublicKey, signedUSK)
|
||||
.signature(userId, masterPublicKey, signedUSK)
|
||||
.build(),
|
||||
selfSignedKey = CryptoCrossSigningKey.Builder(myUserID, KeyUsage.SELF_SIGNING)
|
||||
selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
||||
.key(sskPublicKey)
|
||||
.signature(myUserID, masterPublicKey, signedSSK)
|
||||
.signature(userId, masterPublicKey, signedSSK)
|
||||
.build(),
|
||||
userPasswordAuth = authParams
|
||||
)
|
||||
|
@ -207,16 +204,16 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
this.userPkSigning = userSigningPkOlm
|
||||
this.selfSigningPkSigning = selfSigningPkOlm
|
||||
|
||||
val crossSigningInfo = MXCrossSigningInfo(myUserID, listOf(params.masterKey, params.userKey, params.selfSignedKey))
|
||||
val crossSigningInfo = MXCrossSigningInfo(userId, listOf(params.masterKey, params.userKey, params.selfSignedKey))
|
||||
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
|
||||
cryptoStore.setUserKeysAsTrusted(myUserID)
|
||||
cryptoStore.setUserKeysAsTrusted(userId)
|
||||
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
|
||||
|
||||
uploadSigningKeysTask.configureWith(params) {
|
||||
this.constraints = TaskConstraints(true)
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.i("## CrossSigning - Keys succesfully uploaded")
|
||||
Timber.i("## CrossSigning - Keys successfully uploaded")
|
||||
|
||||
// Sign the current device with SSK
|
||||
val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder()
|
||||
|
@ -225,7 +222,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
|
||||
val signedDevice = selfSigningPkOlm.sign(canonicalJson)
|
||||
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also {
|
||||
it[myUserID] = (it[myUserID]
|
||||
it[userId] = (it[userId]
|
||||
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
|
||||
}
|
||||
myDevice.copy(signatures = updateSignatures).let {
|
||||
|
@ -236,7 +233,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign ->
|
||||
val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap()
|
||||
?: HashMap()).also {
|
||||
it[myUserID] = (it[myUserID]
|
||||
it[userId] = (it[userId]
|
||||
?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign)
|
||||
}
|
||||
mskCrossSigningKeyInfo.copy(
|
||||
|
@ -252,7 +249,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
this.constraints = TaskConstraints(true)
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.i("## CrossSigning - signatures succesfuly uploaded")
|
||||
Timber.i("## CrossSigning - signatures successfully uploaded")
|
||||
callback?.onSuccess(Unit)
|
||||
}
|
||||
|
||||
|
@ -297,29 +294,28 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
||||
* ┃ ALICE ┃ ┃ BOB ┃
|
||||
* ┗━━━━━━━━┛ ┗━━━━━━━━┛
|
||||
* MSK ┌────────────▶MSK
|
||||
* MSK ┌────────────▶ MSK
|
||||
* │
|
||||
* │ │ │
|
||||
* │ SSK │ └──▶ SSK ──────────────────┐
|
||||
* │ │ │
|
||||
* │ │ USK │
|
||||
* └──▶ USK ────────────┘ (not visible by │
|
||||
* Alice) │
|
||||
* ▼
|
||||
* ┌──────────────┐
|
||||
* │ BOB's Device │
|
||||
* └──────────────┘
|
||||
* │ │
|
||||
* │ SSK │
|
||||
* │ │
|
||||
* │ │
|
||||
* └──▶ USK ────────────┘
|
||||
*/
|
||||
override fun isUserTrusted(userId: String): Boolean {
|
||||
override fun isUserTrusted(otherUserId: String): Boolean {
|
||||
return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true
|
||||
}
|
||||
|
||||
override fun isCrossSigningEnabled(): Boolean {
|
||||
return checkSelfTrust().isVerified()
|
||||
}
|
||||
|
||||
/**
|
||||
* Will not force a download of the key, but will verify signatures trust chain
|
||||
*/
|
||||
override fun checkUserTrust(userId: String): UserTrustResult {
|
||||
Timber.d("## CrossSigning checkUserTrust for $userId")
|
||||
if (userId == credentials.userId) {
|
||||
override fun checkUserTrust(otherUserId: String): UserTrustResult {
|
||||
Timber.d("## CrossSigning checkUserTrust for $otherUserId")
|
||||
if (otherUserId == userId) {
|
||||
return checkSelfTrust()
|
||||
}
|
||||
// I trust a user if I trust his master key
|
||||
|
@ -327,25 +323,25 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
// TODO what if the master key is signed by a device key that i have verified
|
||||
|
||||
// First let's get my user key
|
||||
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(credentials.userId)
|
||||
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
||||
|
||||
val myUserKey = myCrossSigningInfo?.userKey()
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(credentials.userId)
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||
|
||||
if (!myCrossSigningInfo.isTrusted()) {
|
||||
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||
}
|
||||
|
||||
// Let's get the other user master key
|
||||
val otherMasterKey = cryptoStore.getCrossSigningInfo(userId)?.masterKey()
|
||||
?: return UserTrustResult.UnknownCrossSignatureInfo(userId)
|
||||
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
|
||||
?: return UserTrustResult.UnknownCrossSignatureInfo(otherUserId)
|
||||
|
||||
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
|
||||
?.get(credentials.userId) // Signatures made by me
|
||||
?.get(userId) // Signatures made by me
|
||||
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
|
||||
|
||||
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
|
||||
Timber.d("## CrossSigning checkUserTrust false for $userId, not signed by my UserSigningKey")
|
||||
Timber.d("## CrossSigning checkUserTrust false for $otherUserId, not signed by my UserSigningKey")
|
||||
return UserTrustResult.KeyNotSigned(otherMasterKey)
|
||||
}
|
||||
|
||||
|
@ -363,12 +359,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
// Special case when it's me,
|
||||
// I have to check that MSK -> USK -> SSK
|
||||
// and that MSK is trusted (i know the private key, or is signed by a trusted device)
|
||||
|
||||
val myUserId = credentials.userId
|
||||
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(myUserId)
|
||||
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
|
||||
|
||||
val myMasterKey = myCrossSigningInfo?.masterKey()
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||
|
||||
// Is the master key trusted
|
||||
// 1) check if I know the private key
|
||||
|
@ -390,9 +384,9 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
olmPkSigning?.releaseSigning()
|
||||
} else {
|
||||
// Maybe it's signed by a locally trusted device?
|
||||
myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) ->
|
||||
val potentialDeviceId = if (key.startsWith("ed25519:")) key.substring("ed25519:".length) else key
|
||||
val potentialDevice = cryptoStore.getUserDevice(myUserId, potentialDeviceId)
|
||||
myMasterKey.signatures?.get(userId)?.forEach { (key, value) ->
|
||||
val potentialDeviceId = key.withoutPrefix("ed25519:")
|
||||
val potentialDevice = cryptoStore.getUserDevice(userId, potentialDeviceId)
|
||||
if (potentialDevice != null && potentialDevice.isVerified) {
|
||||
// Check signature validity?
|
||||
try {
|
||||
|
@ -412,10 +406,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
|
||||
val myUserKey = myCrossSigningInfo.userKey()
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||
|
||||
val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures
|
||||
?.get(myUserId) // Signatures made by me
|
||||
?.get(userId) // Signatures made by me
|
||||
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
|
||||
|
||||
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||
|
@ -431,29 +425,29 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
|
||||
val mySSKey = myCrossSigningInfo.selfSigningKey()
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||
|
||||
val SSKeySignaturesMadeByMyMasterKey = mySSKey.signatures
|
||||
?.get(myUserId) // Signatures made by me
|
||||
val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures
|
||||
?.get(userId) // Signatures made by me
|
||||
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
|
||||
|
||||
if (SSKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||
Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK")
|
||||
return UserTrustResult.KeyNotSigned(mySSKey)
|
||||
}
|
||||
|
||||
// Check that Alice USK signature of Alice MSK is valid
|
||||
try {
|
||||
olmUtility!!.verifyEd25519Signature(SSKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable())
|
||||
olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable())
|
||||
} catch (failure: Throwable) {
|
||||
return UserTrustResult.InvalidSignature(mySSKey, SSKeySignaturesMadeByMyMasterKey)
|
||||
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
||||
}
|
||||
|
||||
return UserTrustResult.Success
|
||||
}
|
||||
|
||||
override fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? {
|
||||
return cryptoStore.getCrossSigningInfo(userId)
|
||||
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return cryptoStore.getCrossSigningInfo(otherUserId)
|
||||
}
|
||||
|
||||
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||
|
@ -468,15 +462,15 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null
|
||||
}
|
||||
|
||||
override fun trustUser(userId: String, callback: MatrixCallback<Unit>) {
|
||||
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
||||
Timber.d("## CrossSigning - Mark user $userId as trusted ")
|
||||
// We should have this user keys
|
||||
val otherMasterKeys = getUserCrossSigningKeys(userId)?.masterKey()
|
||||
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
|
||||
if (otherMasterKeys == null) {
|
||||
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known"))
|
||||
return
|
||||
}
|
||||
val myKeys = getUserCrossSigningKeys(credentials.userId)
|
||||
val myKeys = getUserCrossSigningKeys(userId)
|
||||
if (myKeys == null) {
|
||||
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
|
||||
return
|
||||
|
@ -487,7 +481,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
// Sign the other MasterKey with our UserSiging key
|
||||
// Sign the other MasterKey with our UserSigning key
|
||||
val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java,
|
||||
otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) }
|
||||
|
||||
|
@ -497,12 +491,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
cryptoStore.setUserKeysAsTrusted(userId, true)
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, true)
|
||||
// TODO update local copy with new signature directly here? kind of local echo of trust?
|
||||
|
||||
Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK")
|
||||
val uploadQuery = UploadSignatureQueryBuilder()
|
||||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(credentials.userId, userPubKey, newSignature))
|
||||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.callback = callback
|
||||
|
@ -511,13 +505,13 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
|
||||
override fun signDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||
// This device should be yours
|
||||
val device = cryptoStore.getUserDevice(credentials.userId, deviceId)
|
||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||
if (device == null) {
|
||||
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
|
||||
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
|
||||
return
|
||||
}
|
||||
|
||||
val myKeys = getUserCrossSigningKeys(credentials.userId)
|
||||
val myKeys = getUserCrossSigningKeys(userId)
|
||||
if (myKeys == null) {
|
||||
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
|
||||
return
|
||||
|
@ -539,7 +533,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
val toUpload = device.copy(
|
||||
signatures = mapOf(
|
||||
credentials.userId
|
||||
userId
|
||||
to
|
||||
mapOf(
|
||||
"ed25519:$ssPubKey" to newSignature
|
||||
|
@ -555,17 +549,17 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun checkDeviceTrust(userId: String, deviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||
val otherDevice = cryptoStore.getUserDevice(userId, deviceId)
|
||||
?: return DeviceTrustResult.UnknownDevice(deviceId)
|
||||
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
|
||||
?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
|
||||
|
||||
val myKeys = getUserCrossSigningKeys(credentials.userId)
|
||||
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(credentials.userId))
|
||||
val myKeys = getUserCrossSigningKeys(userId)
|
||||
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
|
||||
|
||||
if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys))
|
||||
|
||||
val otherKeys = getUserCrossSigningKeys(userId)
|
||||
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
|
||||
val otherKeys = getUserCrossSigningKeys(otherUserId)
|
||||
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId))
|
||||
|
||||
// TODO should we force verification ?
|
||||
if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys))
|
||||
|
@ -589,15 +583,15 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
* └──────────────┘
|
||||
*/
|
||||
|
||||
val otherSSKSignature = otherDevice.signatures?.get(userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
|
||||
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.MissingDeviceSignature(deviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
||||
val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
|
||||
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
||||
?: ""))
|
||||
|
||||
// Check bob's device is signed by bob's SSK
|
||||
try {
|
||||
olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
|
||||
} catch (e: Throwable) {
|
||||
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(deviceId, otherSSKSignature, e))
|
||||
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e))
|
||||
}
|
||||
|
||||
return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted))
|
||||
|
@ -614,19 +608,19 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
override fun onUsersDeviceUpdate(users: List<String>) {
|
||||
|
||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
|
||||
users.forEach { userId ->
|
||||
users.forEach { otherUserId ->
|
||||
|
||||
checkUserTrust(userId).let {
|
||||
Timber.d("## CrossSigning - update trust for ${userId} , verified=${it.isVerified()}")
|
||||
cryptoStore.setUserKeysAsTrusted(userId, it.isVerified())
|
||||
checkUserTrust(otherUserId).let {
|
||||
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, it.isVerified())
|
||||
}
|
||||
|
||||
// TODO if my keys have changes, i should recheck all devices of all users?
|
||||
val devices = cryptoStore.getUserDeviceList(userId)
|
||||
val devices = cryptoStore.getUserDeviceList(otherUserId)
|
||||
devices?.forEach { device ->
|
||||
val updatedTrust = checkDeviceTrust(userId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user ${userId} , verified=$updatedTrust")
|
||||
cryptoStore.setDeviceTrust(userId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
|
||||
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
|
||||
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,30 +34,48 @@ internal data class KeyVerificationStart(
|
|||
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
|
||||
@Json(name = "hashes") override val hashes: List<String>? = null,
|
||||
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>? = null,
|
||||
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>? = null
|
||||
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>? = null,
|
||||
// For QR code verification
|
||||
@Json(name = "secret") override val sharedSecret: String? = null
|
||||
) : SendToDeviceObject, VerificationInfoStart {
|
||||
|
||||
override fun toCanonicalJson(): String? {
|
||||
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
|
||||
}
|
||||
|
||||
// TODO Move those method to the interface?
|
||||
override fun isValid(): Boolean {
|
||||
if (transactionID.isNullOrBlank()
|
||||
|| fromDevice.isNullOrBlank()
|
||||
|| method !in supportedVerificationMethods
|
||||
|| keyAgreementProtocols.isNullOrEmpty()
|
||||
|| hashes.isNullOrEmpty()
|
||||
|| !hashes.contains("sha256")
|
||||
|| messageAuthenticationCodes.isNullOrEmpty()
|
||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|
||||
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
|
||||
Timber.e("## received invalid verification request")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidSas(): Boolean {
|
||||
if (keyAgreementProtocols.isNullOrEmpty()
|
||||
|| hashes.isNullOrEmpty()
|
||||
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|
||||
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
|
||||
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|
||||
|| shortAuthenticationStrings.isNullOrEmpty()
|
||||
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isValidReciprocate(): Boolean {
|
||||
if (sharedSecret.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
}
|
||||
|
|
|
@ -33,11 +33,3 @@ internal fun VerificationMethod.toValue(): String {
|
|||
VerificationMethod.QR_CODE_SHOW -> VERIFICATION_METHOD_QR_CODE_SHOW
|
||||
}
|
||||
}
|
||||
|
||||
internal val supportedVerificationMethods =
|
||||
listOf(
|
||||
VERIFICATION_METHOD_SAS,
|
||||
VERIFICATION_METHOD_QR_CODE_SHOW,
|
||||
VERIFICATION_METHOD_QR_CODE_SCAN,
|
||||
VERIFICATION_METHOD_RECIPROCATE
|
||||
)
|
||||
|
|
|
@ -17,7 +17,6 @@ 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.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||
|
@ -30,23 +29,25 @@ import timber.log.Timber
|
|||
|
||||
internal class DefaultIncomingSASDefaultVerificationTransaction(
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
override val credentials: Credentials,
|
||||
override val userId: String,
|
||||
override val deviceId: String?,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
crossSigningService: CrossSigningService,
|
||||
deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserID: String,
|
||||
val autoAccept: Boolean = false
|
||||
private val autoAccept: Boolean = false
|
||||
) : SASDefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
userId,
|
||||
deviceId,
|
||||
cryptoStore,
|
||||
crossSigningService,
|
||||
deviceFingerprint,
|
||||
transactionId,
|
||||
otherUserID,
|
||||
null,
|
||||
true),
|
||||
isIncoming = true),
|
||||
IncomingSasVerificationTransaction {
|
||||
|
||||
override val uxState: IncomingSasVerificationTransaction.UxState
|
||||
|
@ -157,7 +158,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) {
|
||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
||||
Timber.v("## SAS received key for request id:$transactionId")
|
||||
if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) {
|
||||
Timber.e("## SAS received key from invalid state $state")
|
||||
|
@ -194,10 +195,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
// - the Matrix ID of the user who sent the m.key.verification.accept message,
|
||||
// - he device ID of the device that sent the m.key.verification.accept message
|
||||
// - the transaction ID.
|
||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS" +
|
||||
"$otherUserId$otherDeviceId" +
|
||||
"${credentials.userId}${credentials.deviceId}" +
|
||||
transactionId
|
||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId"
|
||||
// decimal: generate five bytes by using HKDF.
|
||||
// emoji: generate six bytes by using HKDF.
|
||||
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
|
||||
|
|
|
@ -15,21 +15,19 @@
|
|||
*/
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
|
||||
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||
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.toValue
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultOutgoingSASDefaultVerificationRequest(
|
||||
internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||
setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
credentials: Credentials,
|
||||
userId: String,
|
||||
deviceId: String?,
|
||||
cryptoStore: IMXCryptoStore,
|
||||
crossSigningService: CrossSigningService,
|
||||
deviceFingerprint: String,
|
||||
|
@ -38,7 +36,8 @@ internal class DefaultOutgoingSASDefaultVerificationRequest(
|
|||
otherDeviceId: String
|
||||
) : SASDefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
userId,
|
||||
deviceId,
|
||||
cryptoStore,
|
||||
crossSigningService,
|
||||
deviceFingerprint,
|
||||
|
@ -46,27 +45,27 @@ internal class DefaultOutgoingSASDefaultVerificationRequest(
|
|||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming = false),
|
||||
OutgoingSasVerificationRequest {
|
||||
OutgoingSasVerificationTransaction {
|
||||
|
||||
override val uxState: OutgoingSasVerificationRequest.UxState
|
||||
override val uxState: OutgoingSasVerificationTransaction.UxState
|
||||
get() {
|
||||
return when (state) {
|
||||
VerificationTxState.None -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_START
|
||||
VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START
|
||||
VerificationTxState.SendingStart,
|
||||
VerificationTxState.Started,
|
||||
VerificationTxState.OnAccepted,
|
||||
VerificationTxState.SendingKey,
|
||||
VerificationTxState.KeySent,
|
||||
VerificationTxState.OnKeyReceived -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT
|
||||
VerificationTxState.ShortCodeReady -> OutgoingSasVerificationRequest.UxState.SHOW_SAS
|
||||
VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
|
||||
VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS
|
||||
VerificationTxState.ShortCodeAccepted,
|
||||
VerificationTxState.SendingMac,
|
||||
VerificationTxState.MacSent,
|
||||
VerificationTxState.Verifying -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION
|
||||
VerificationTxState.Verified -> OutgoingSasVerificationRequest.UxState.VERIFIED
|
||||
VerificationTxState.OnCancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME
|
||||
VerificationTxState.Cancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER
|
||||
else -> OutgoingSasVerificationRequest.UxState.UNKNOWN
|
||||
VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
|
||||
VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED
|
||||
VerificationTxState.OnCancelled -> OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME
|
||||
VerificationTxState.Cancelled -> OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
|
||||
else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,16 +74,15 @@ internal class DefaultOutgoingSASDefaultVerificationRequest(
|
|||
cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
|
||||
fun start(method: VerificationMethod) {
|
||||
fun start() {
|
||||
if (state != VerificationTxState.None) {
|
||||
Timber.e("## SAS O: start verification from invalid state")
|
||||
// should I cancel??
|
||||
throw IllegalStateException("Interactive Key verification already started")
|
||||
}
|
||||
|
||||
val startMessage = transport.createStart(
|
||||
credentials.deviceId ?: "",
|
||||
method.toValue(),
|
||||
val startMessage = transport.createStartForSas(
|
||||
deviceId ?: "",
|
||||
transactionId,
|
||||
KNOWN_AGREEMENT_PROTOCOLS,
|
||||
KNOWN_HASHES,
|
||||
|
@ -164,7 +162,7 @@ internal class DefaultOutgoingSASDefaultVerificationRequest(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) {
|
||||
override fun onKeyVerificationKey(vKey: VerificationInfoKey) {
|
||||
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
|
||||
if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) {
|
||||
Timber.e("## received key from invalid state $state")
|
||||
|
@ -192,16 +190,13 @@ internal class DefaultOutgoingSASDefaultVerificationRequest(
|
|||
// - the Matrix ID of the user who sent the m.key.verification.accept message,
|
||||
// - he device ID of the device that sent the m.key.verification.accept message
|
||||
// - the transaction ID.
|
||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS" +
|
||||
"${credentials.userId}${credentials.deviceId}" +
|
||||
"$otherUserId$otherDeviceId" +
|
||||
transactionId
|
||||
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId"
|
||||
// decimal: generate five bytes by using HKDF.
|
||||
// emoji: generate six bytes by using HKDF.
|
||||
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
|
||||
state = VerificationTxState.ShortCodeReady
|
||||
} else {
|
||||
// bad commitement
|
||||
// bad commitment
|
||||
cancel(CancelCode.MismatchedCommitment)
|
||||
}
|
||||
}
|
|
@ -20,45 +20,74 @@ import android.os.Handler
|
|||
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.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.*
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||
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.LocalEcho
|
||||
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.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationAcceptContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationCancelContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationDoneContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationKeyContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationMacContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
|
||||
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
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.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.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.toValue
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.verification.qrcode.DefaultQrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.qrcode.QrCodeData
|
||||
import im.vector.matrix.android.internal.crypto.verification.qrcode.generateSharedSecret
|
||||
import im.vector.matrix.android.internal.di.DeviceId
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.toImmutableList
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.set
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultVerificationService @Inject constructor(
|
||||
private val credentials: Credentials,
|
||||
@UserId private val userId: String,
|
||||
@DeviceId private val deviceId: String?,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
|
||||
private val deviceListManager: DeviceListManager,
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory,
|
||||
private val sasTransportToDeviceFactory: SasTransportToDeviceFactory,
|
||||
private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
|
||||
private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
|
||||
private val crossSigningService: CrossSigningService
|
||||
) : DefaultVerificationTransaction.Listener, VerificationService {
|
||||
|
||||
|
@ -236,7 +265,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
?: return
|
||||
val senderId = event.senderId ?: return
|
||||
|
||||
if (requestInfo.toUserId != credentials.userId) {
|
||||
if (requestInfo.toUserId != userId) {
|
||||
// I should ignore this, it's not for me
|
||||
Timber.w("## SAS Verification ignoring request from ${event.senderId}, not sent to me")
|
||||
return
|
||||
|
@ -289,7 +318,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
if (startReq?.isValid()?.not() == true) {
|
||||
Timber.e("## received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
.cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
|
@ -301,9 +330,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
handleStart(otherUserId, startReq as VerificationInfoStart) {
|
||||
it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
|
||||
it.transport = verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
|
||||
}?.let {
|
||||
sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
|
||||
.cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
|
@ -322,7 +351,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
if (!startReq.isValid()) {
|
||||
Timber.e("## SAS received invalid verification request")
|
||||
if (startReq.transactionID != null) {
|
||||
sasTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID,
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
|
@ -333,9 +362,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
// Download device keys prior to everything
|
||||
handleStart(otherUserId, startReq) {
|
||||
it.transport = sasTransportToDeviceFactory.createTransport(it)
|
||||
it.transport = verificationTransportToDeviceFactory.createTransport(it)
|
||||
}?.let {
|
||||
sasTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
verificationTransportToDeviceFactory.createTransport(null).cancelTransaction(
|
||||
startReq.transactionID ?: "",
|
||||
otherUserId!!,
|
||||
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||
|
@ -344,30 +373,46 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a CancelCode to make the caller cancel the verification. Else return null
|
||||
*/
|
||||
private suspend fun handleStart(otherUserId: String?, startReq: VerificationInfoStart, txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? {
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}")
|
||||
Timber.d("## SAS onStartRequestReceived ${startReq.transactionID}")
|
||||
if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice ?: "") != null) {
|
||||
Timber.v("## SAS onStartRequestReceived $startReq")
|
||||
val tid = startReq.transactionID!!
|
||||
val existing = getExistingTransaction(otherUserId, tid)
|
||||
val existingTxs = getExistingTransactionsForUser(otherUserId)
|
||||
if (existing != null) {
|
||||
// should cancel both!
|
||||
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
|
||||
existing.cancel(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)
|
||||
}
|
||||
return CancelCode.UnexpectedMessage
|
||||
// cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
} else {
|
||||
// Ok we can create
|
||||
if (startReq.method == VERIFICATION_METHOD_SAS) {
|
||||
|
||||
when (startReq.method) {
|
||||
VERIFICATION_METHOD_SAS -> {
|
||||
when (existing) {
|
||||
is SasVerificationTransaction -> {
|
||||
// should cancel both!
|
||||
Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionID}")
|
||||
existing.cancel(CancelCode.UnexpectedMessage)
|
||||
// Already cancelled, so return null
|
||||
return null
|
||||
}
|
||||
is QrCodeVerificationTransaction -> {
|
||||
// Nothing to do?
|
||||
}
|
||||
null -> {
|
||||
getExistingTransactionsForUser(otherUserId)
|
||||
?.filterIsInstance(SasVerificationTransaction::class.java)
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.also {
|
||||
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
|
||||
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID}")
|
||||
}
|
||||
?.forEach {
|
||||
it.cancel(CancelCode.UnexpectedMessage)
|
||||
}
|
||||
?.also {
|
||||
return CancelCode.UnexpectedMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ok we can create a SAS transaction
|
||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||
// If there is a corresponding request, we can auto accept
|
||||
// as we are the one requesting in first place (or we accepted the request)
|
||||
|
@ -376,7 +421,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
val tx = DefaultIncomingSASDefaultVerificationTransaction(
|
||||
// this,
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
userId,
|
||||
deviceId,
|
||||
cryptoStore,
|
||||
crossSigningService,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
|
@ -385,30 +431,39 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
autoAccept).also { txConfigure(it) }
|
||||
addTransaction(tx)
|
||||
tx.acceptVerificationEvent(otherUserId, startReq)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
VERIFICATION_METHOD_RECIPROCATE -> {
|
||||
// Other user has scanned my QR code
|
||||
if (existing is DefaultQrCodeVerificationTransaction) {
|
||||
existing.onStartReceived(startReq)
|
||||
return null
|
||||
} else {
|
||||
Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionID}")
|
||||
return CancelCode.UnexpectedMessage
|
||||
}
|
||||
}
|
||||
else ->{
|
||||
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
||||
return CancelCode.UnknownMethod
|
||||
// cancelTransaction(tid, otherUserId, startReq.fromDevice
|
||||
// ?: event.getSenderKey()!!, CancelCode.UnknownMethod)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return CancelCode.UnexpectedMessage
|
||||
// cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO Refacto: It could just return a boolean
|
||||
private suspend fun checkKeysAreDownloaded(otherUserId: String,
|
||||
fromDevice: String): MXUsersDevicesMap<CryptoDeviceInfo>? {
|
||||
otherDeviceId: String): MXUsersDevicesMap<CryptoDeviceInfo>? {
|
||||
return try {
|
||||
var keys = deviceListManager.downloadKeys(listOf(otherUserId), false)
|
||||
if (keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true) {
|
||||
if (keys.getUserDeviceIds(otherUserId)?.contains(otherDeviceId) == true) {
|
||||
return keys
|
||||
} else {
|
||||
// force download
|
||||
keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
|
||||
return keys.takeIf { keys.getUserDeviceIds(otherUserId)?.contains(fromDevice) == true }
|
||||
return keys.takeIf { keys.getUserDeviceIds(otherUserId)?.contains(otherDeviceId) == true }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
|
@ -573,12 +628,12 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
return
|
||||
}
|
||||
if (checkKeysAreDownloaded(event.senderId, readyReq.fromDevice ?: "") == null) {
|
||||
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not knwown")
|
||||
Timber.e("## SAS Verification device ${readyReq.fromDevice} is not known")
|
||||
// TODO cancel?
|
||||
return
|
||||
}
|
||||
|
||||
handleReadyReceived(event.senderId, readyReq)
|
||||
handleReadyReceived(event.senderId, event.roomId!!, readyReq)
|
||||
}
|
||||
|
||||
private fun onRoomDoneReceived(event: Event) {
|
||||
|
@ -623,13 +678,87 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleReadyReceived(senderId: String, readyReq: VerificationInfoReady) {
|
||||
private fun handleReadyReceived(senderId: String, roomId: String, readyReq: VerificationInfoReady) {
|
||||
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID }
|
||||
if (existingRequest == null) {
|
||||
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}")
|
||||
return
|
||||
}
|
||||
updatePendingRequest(existingRequest.copy(readyInfo = readyReq))
|
||||
|
||||
val qrCodeData = readyReq.methods
|
||||
// Check if other user is able to scan QR code
|
||||
?.takeIf { it.contains(VERIFICATION_METHOD_QR_CODE_SCAN) }
|
||||
?.let {
|
||||
createQrCodeData(existingRequest.transactionId, existingRequest.otherUserId)
|
||||
}
|
||||
|
||||
if (readyReq.methods?.orEmpty().orEmpty().contains(VERIFICATION_METHOD_RECIPROCATE)) {
|
||||
// Create the pending transaction
|
||||
val tx = DefaultQrCodeVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
readyReq.transactionID!!,
|
||||
senderId,
|
||||
readyReq.fromDevice,
|
||||
crossSigningService,
|
||||
cryptoStore,
|
||||
qrCodeData,
|
||||
userId,
|
||||
deviceId ?: "",
|
||||
false)
|
||||
|
||||
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
|
||||
|
||||
addTransaction(tx)
|
||||
}
|
||||
|
||||
updatePendingRequest(existingRequest.copy(
|
||||
readyInfo = readyReq
|
||||
))
|
||||
}
|
||||
|
||||
private fun createQrCodeData(transactionId: String?, otherUserId: String): QrCodeData? {
|
||||
// Build the QR code URL
|
||||
val requestEventId = transactionId ?: run {
|
||||
Timber.w("## Unknown requestEventId")
|
||||
return null
|
||||
}
|
||||
|
||||
val myMasterKey = crossSigningService.getMyCrossSigningKeys()
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
?: run {
|
||||
Timber.w("## Unable to get my master key")
|
||||
return null
|
||||
}
|
||||
|
||||
val otherUserMasterKey = crossSigningService.getUserCrossSigningKeys(otherUserId)
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
?: run {
|
||||
Timber.w("## Unable to get other user master key")
|
||||
return null
|
||||
}
|
||||
|
||||
val myDeviceId = deviceId
|
||||
?: run {
|
||||
Timber.w("## Unable to get my deviceId")
|
||||
return null
|
||||
}
|
||||
|
||||
val myDeviceKey = myDeviceInfoHolder.get().myDevice.fingerprint()!!
|
||||
|
||||
val generatedSharedSecret = generateSharedSecret()
|
||||
return QrCodeData(
|
||||
userId = userId,
|
||||
requestEventId = requestEventId,
|
||||
action = QrCodeData.ACTION_VERIFY,
|
||||
keys = hashMapOf(
|
||||
myMasterKey to myMasterKey,
|
||||
myDeviceId to myDeviceKey
|
||||
),
|
||||
sharedSecret = generatedSharedSecret,
|
||||
otherUserKey = otherUserMasterKey
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleDoneReceived(senderId: String, doneInfo: VerificationInfo) {
|
||||
|
@ -641,21 +770,22 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
updatePendingRequest(existingRequest.copy(isSuccessful = true))
|
||||
}
|
||||
|
||||
override fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? {
|
||||
// TODO All this methods should be delegated to a TransactionStore
|
||||
override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? {
|
||||
synchronized(lock = txMap) {
|
||||
return txMap[otherUser]?.get(tid)
|
||||
return txMap[otherUserId]?.get(tid)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExistingVerificationRequest(otherUser: String): List<PendingVerificationRequest>? {
|
||||
override fun getExistingVerificationRequest(otherUserId: String): List<PendingVerificationRequest>? {
|
||||
synchronized(lock = pendingRequests) {
|
||||
return pendingRequests[otherUser]
|
||||
return pendingRequests[otherUserId]
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? {
|
||||
override fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? {
|
||||
synchronized(lock = pendingRequests) {
|
||||
return tid?.let { tid -> pendingRequests[otherUser]?.firstOrNull { it.transactionId == tid } }
|
||||
return tid?.let { tid -> pendingRequests[otherUserId]?.firstOrNull { it.transactionId == tid } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -692,39 +822,40 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String? {
|
||||
val txID = createUniqueIDForTransaction(userId, deviceID)
|
||||
override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceID: String): String? {
|
||||
val txID = createUniqueIDForTransaction(otherUserId, otherDeviceID)
|
||||
// should check if already one (and cancel it)
|
||||
if (method == VerificationMethod.SAS) {
|
||||
val tx = DefaultOutgoingSASDefaultVerificationRequest(
|
||||
val tx = DefaultOutgoingSASDefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
userId,
|
||||
deviceId,
|
||||
cryptoStore,
|
||||
crossSigningService,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
txID,
|
||||
userId,
|
||||
deviceID)
|
||||
tx.transport = sasTransportToDeviceFactory.createTransport(tx)
|
||||
otherUserId,
|
||||
otherDeviceID)
|
||||
tx.transport = verificationTransportToDeviceFactory.createTransport(tx)
|
||||
addTransaction(tx)
|
||||
|
||||
tx.start(method)
|
||||
tx.start()
|
||||
return txID
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown verification method")
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String, localId: String?)
|
||||
override fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, otherUserId: String, roomId: String, localId: String?)
|
||||
: PendingVerificationRequest {
|
||||
Timber.i("## SAS Requesting verification to user: $userId in room $roomId")
|
||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||
|
||||
val requestsForUser = pendingRequests[userId]
|
||||
val requestsForUser = pendingRequests[otherUserId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
pendingRequests[userId] = it
|
||||
pendingRequests[otherUserId] = it
|
||||
}
|
||||
|
||||
val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
|
||||
// Cancel existing pending requests?
|
||||
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||
|
@ -744,10 +875,24 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
isIncoming = false,
|
||||
roomId = roomId,
|
||||
localID = localID,
|
||||
otherUserId = userId
|
||||
otherUserId = otherUserId
|
||||
)
|
||||
|
||||
transport.sendVerificationRequest(methods.map { it.toValue() }, localID, userId, roomId) { syncedId, info ->
|
||||
// We can SCAN or SHOW QR codes only if cross-signing is enabled
|
||||
val methodValues = if (crossSigningService.isCrossSigningEnabled()) {
|
||||
// Add reciprocate method if application declares it can scan or show QR codes
|
||||
// Not sure if it ok to do that (?)
|
||||
val reciprocateMethod = methods.firstOrNull { it == VerificationMethod.QR_CODE_SCAN || it == VerificationMethod.QR_CODE_SHOW }?.let { listOf(VERIFICATION_METHOD_RECIPROCATE) }.orEmpty()
|
||||
methods.map { it.toValue() } + reciprocateMethod
|
||||
} else {
|
||||
// Filter out SCAN and SHOW qr code method
|
||||
methods
|
||||
.filter { it != VerificationMethod.QR_CODE_SHOW && it != VerificationMethod.QR_CODE_SCAN }
|
||||
.map { it.toValue() }
|
||||
}
|
||||
.distinct()
|
||||
|
||||
transport.sendVerificationRequest(methodValues, localID, otherUserId, roomId) { syncedId, info ->
|
||||
// We need to update with the syncedID
|
||||
updatePendingRequest(verificationRequest.copy(
|
||||
transactionId = syncedId,
|
||||
|
@ -762,7 +907,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
|
||||
sasTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
.cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)
|
||||
|
||||
getExistingVerificationRequest(otherUserId, transactionId)?.let {
|
||||
|
@ -795,42 +940,52 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
otherDeviceId: String,
|
||||
callback: MatrixCallback<String>?): String? {
|
||||
if (method == VerificationMethod.SAS) {
|
||||
val tx = DefaultOutgoingSASDefaultVerificationRequest(
|
||||
val tx = DefaultOutgoingSASDefaultVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
credentials,
|
||||
userId,
|
||||
deviceId,
|
||||
cryptoStore,
|
||||
crossSigningService,
|
||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId)
|
||||
tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, tx)
|
||||
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
|
||||
addTransaction(tx)
|
||||
|
||||
tx.start(method)
|
||||
tx.start()
|
||||
return transactionId
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown verification method")
|
||||
}
|
||||
}
|
||||
|
||||
override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean {
|
||||
override fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
transactionId: String): Boolean {
|
||||
Timber.v("## SAS readyPendingVerificationInDMs $otherUserId room:$roomId tx:$transactionId")
|
||||
// Let's find the related request
|
||||
val existingRequest = getExistingVerificationRequest(otherUserId, transactionId)
|
||||
if (existingRequest != null) {
|
||||
// we need to send a ready event, with matching methods
|
||||
val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
// TODO We should not use supportedVerificationMethods here, because it depends on the client implementation
|
||||
val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList()
|
||||
val transport = verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||
val computedMethods = computeReadyMethods(
|
||||
transactionId,
|
||||
otherUserId,
|
||||
existingRequest.requestInfo?.fromDevice ?: "",
|
||||
roomId,
|
||||
existingRequest.requestInfo?.methods,
|
||||
methods)
|
||||
if (methods.isNullOrEmpty()) {
|
||||
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
||||
// TODO buttons should not be shown in this case?
|
||||
return false
|
||||
}
|
||||
// TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
|
||||
val readyMsg = transport.createReady(transactionId, credentials.deviceId ?: "", methods)
|
||||
transport.sendToOther(EventType.KEY_VERIFICATION_READY, readyMsg,
|
||||
val readyMsg = transport.createReady(transactionId, deviceId ?: "", computedMethods)
|
||||
transport.sendToOther(EventType.KEY_VERIFICATION_READY,
|
||||
readyMsg,
|
||||
VerificationTxState.None,
|
||||
CancelCode.User,
|
||||
null // TODO handle error?
|
||||
|
@ -844,15 +999,73 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun computeReadyMethods(
|
||||
transactionId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
roomId: String,
|
||||
otherUserMethods: List<String>?,
|
||||
methods: List<VerificationMethod>): List<String> {
|
||||
if (otherUserMethods.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val result = mutableSetOf<String>()
|
||||
|
||||
if (VERIFICATION_METHOD_SAS in otherUserMethods && VerificationMethod.SAS in methods) {
|
||||
// Other can do SAS and so do I
|
||||
result.add(VERIFICATION_METHOD_SAS)
|
||||
}
|
||||
|
||||
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods || VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods) {
|
||||
// Other user wants to verify using QR code. Cross-signing has to be setup
|
||||
val qrCodeData = createQrCodeData(transactionId, otherUserId)
|
||||
|
||||
if (qrCodeData != null) {
|
||||
if (VERIFICATION_METHOD_QR_CODE_SCAN in otherUserMethods && VerificationMethod.QR_CODE_SHOW in methods) {
|
||||
// Other can Scan and I can show QR code
|
||||
result.add(VERIFICATION_METHOD_QR_CODE_SHOW)
|
||||
result.add(VERIFICATION_METHOD_RECIPROCATE)
|
||||
}
|
||||
if (VERIFICATION_METHOD_QR_CODE_SHOW in otherUserMethods && VerificationMethod.QR_CODE_SCAN in methods) {
|
||||
// Other can show and I can scan QR code
|
||||
result.add(VERIFICATION_METHOD_QR_CODE_SCAN)
|
||||
result.add(VERIFICATION_METHOD_RECIPROCATE)
|
||||
}
|
||||
}
|
||||
|
||||
if (VERIFICATION_METHOD_RECIPROCATE in result) {
|
||||
// Create the pending transaction
|
||||
val tx = DefaultQrCodeVerificationTransaction(
|
||||
setDeviceVerificationAction,
|
||||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
crossSigningService,
|
||||
cryptoStore,
|
||||
qrCodeData,
|
||||
userId,
|
||||
deviceId ?: "",
|
||||
false)
|
||||
|
||||
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
|
||||
|
||||
addTransaction(tx)
|
||||
}
|
||||
}
|
||||
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
|
||||
*/
|
||||
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
|
||||
private fun createUniqueIDForTransaction(otherUserId: String, otherDeviceID: String): String {
|
||||
return buildString {
|
||||
append(credentials.userId).append("|")
|
||||
append(credentials.deviceId).append("|")
|
||||
append(userId).append("|")
|
||||
append(deviceID).append("|")
|
||||
append(deviceId).append("|")
|
||||
append(otherUserId).append("|")
|
||||
append(otherDeviceID).append("|")
|
||||
append(UUID.randomUUID().toString())
|
||||
}
|
||||
}
|
||||
|
@ -867,5 +1080,13 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// remove
|
||||
this.removeTransaction(tx.otherUserId, tx.transactionId)
|
||||
}
|
||||
if (tx is QrCodeVerificationTransaction
|
||||
&& (tx.state == VerificationTxState.Cancelled
|
||||
|| tx.state == VerificationTxState.OnCancelled
|
||||
|| tx.state == VerificationTxState.Verified)
|
||||
) {
|
||||
// remove
|
||||
this.removeTransaction(tx.otherUserId, tx.transactionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.UUID
|
|||
|
||||
/**
|
||||
* Stores current pending verification requests
|
||||
* TODO We should not expose this whole object to the app. Create an interface
|
||||
*/
|
||||
data class PendingVerificationRequest(
|
||||
val ageLocalTs: Long,
|
||||
|
@ -38,7 +39,6 @@ data class PendingVerificationRequest(
|
|||
val cancelConclusion: CancelCode? = null,
|
||||
val isSuccessful: Boolean = false,
|
||||
val handledByOtherSession: Boolean = false
|
||||
|
||||
) {
|
||||
val isReady: Boolean = readyInfo != null
|
||||
val isSent: Boolean = transactionId != null
|
||||
|
|
|
@ -17,7 +17,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.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
|
||||
|
@ -31,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.MXKey
|
|||
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||
import org.matrix.olm.OlmSAS
|
||||
import org.matrix.olm.OlmUtility
|
||||
import timber.log.Timber
|
||||
|
@ -41,15 +41,16 @@ import kotlin.properties.Delegates
|
|||
*/
|
||||
internal abstract class SASDefaultVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
open val credentials: Credentials,
|
||||
open val userId: String,
|
||||
open val deviceId: String?,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val deviceFingerprint: String,
|
||||
transactionId: String,
|
||||
otherUserId: String,
|
||||
otherDevice: String?,
|
||||
isIncoming: Boolean) :
|
||||
DefaultVerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming), SasVerificationTransaction {
|
||||
otherDeviceId: String?,
|
||||
isIncoming: Boolean
|
||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), SasVerificationTransaction {
|
||||
|
||||
companion object {
|
||||
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
|
||||
|
@ -139,11 +140,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
// - the device ID of the device receiving the MAC,
|
||||
// - the transaction ID, and
|
||||
// - the key ID of the key being MAC-ed, or the string “KEY_IDS” if the item being MAC-ed is the list of key IDs.
|
||||
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" +
|
||||
credentials.userId + credentials.deviceId +
|
||||
otherUserId + otherDeviceId +
|
||||
transactionId
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$userId$deviceId$otherUserId$otherDeviceId$transactionId"
|
||||
|
||||
// Previously, with SAS verification, the m.key.verification.mac message only contained the user's device key.
|
||||
// It should now contain both the device key and the MSK.
|
||||
|
@ -151,7 +148,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
|
||||
val keyMap = HashMap<String, String>()
|
||||
|
||||
val keyId = "ed25519:${credentials.deviceId}"
|
||||
val keyId = "ed25519:$deviceId"
|
||||
val macString = macUsingAgreedMethod(deviceFingerprint, baseInfo + keyId)
|
||||
|
||||
if (macString.isNullOrBlank()) {
|
||||
|
@ -211,7 +208,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
when (info) {
|
||||
is VerificationInfoStart -> onVerificationStart(info)
|
||||
is VerificationInfoAccept -> onVerificationAccept(info)
|
||||
is VerificationInfoKey -> onKeyVerificationKey(senderId, info)
|
||||
is VerificationInfoKey -> onKeyVerificationKey(info)
|
||||
is VerificationInfoMac -> onKeyVerificationMac(info)
|
||||
else -> {
|
||||
// nop
|
||||
|
@ -223,7 +220,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
|
||||
abstract fun onVerificationAccept(accept: VerificationInfoAccept)
|
||||
|
||||
abstract fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey)
|
||||
abstract fun onKeyVerificationKey(vKey: VerificationInfoKey)
|
||||
|
||||
abstract fun onKeyVerificationMac(vKey: VerificationInfoMac)
|
||||
|
||||
|
@ -241,7 +238,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
|
||||
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC" +
|
||||
otherUserId + otherDeviceId +
|
||||
credentials.userId + credentials.deviceId +
|
||||
userId + deviceId +
|
||||
transactionId
|
||||
|
||||
val commaSeparatedListOfKeyIds = theirMac!!.mac!!.keys.sorted().joinToString(",")
|
||||
|
@ -257,7 +254,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
|
||||
// cannot be empty because it has been validated
|
||||
theirMac!!.mac!!.keys.forEach {
|
||||
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
|
||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
||||
if (otherDeviceKey == null) {
|
||||
Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify")
|
||||
|
@ -280,7 +277,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
if (otherCrossSigningMasterKeyPublic != null) {
|
||||
// Did the user signed his master key
|
||||
theirMac!!.mac!!.keys.forEach {
|
||||
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
|
||||
val keyIDNoPrefix = it.withoutPrefix("ed25519:")
|
||||
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
||||
// Check the signature
|
||||
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
||||
|
@ -305,7 +302,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
}
|
||||
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (otherMasterKeyIsVerified && otherUserId != credentials.userId) {
|
||||
if (otherMasterKeyIsVerified && otherUserId != userId) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
|
@ -315,9 +312,9 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
})
|
||||
}
|
||||
|
||||
if (otherUserId == credentials.userId) {
|
||||
if (otherUserId == userId) {
|
||||
// If me it's reasonable to sign and upload the device signature
|
||||
// Notice that i might not have the private keys, so may ot be able to do it
|
||||
// Notice that i might not have the private keys, so may not be able to do it
|
||||
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId")
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification
|
|||
internal interface VerificationInfoStart : VerificationInfo {
|
||||
|
||||
val method: String?
|
||||
|
||||
/**
|
||||
* Alice’s device ID
|
||||
*/
|
||||
|
@ -58,5 +59,10 @@ internal interface VerificationInfoStart : VerificationInfo {
|
|||
*/
|
||||
val shortAuthenticationStrings: List<String>?
|
||||
|
||||
/**
|
||||
* Shared secret, when starting verification with QR code
|
||||
*/
|
||||
val sharedSecret: String?
|
||||
|
||||
fun toCanonicalJson(): String?
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
|
||||
/**
|
||||
* SAS verification can be performed using toDevice events or via DM.
|
||||
* This class abstracts the concept of transport for SAS
|
||||
* Verification can be performed using toDevice events or via DM.
|
||||
* This class abstracts the concept of transport for verification
|
||||
*/
|
||||
internal interface VerificationTransport {
|
||||
|
||||
|
@ -46,6 +46,7 @@ internal interface VerificationTransport {
|
|||
code: CancelCode)
|
||||
|
||||
fun done(transactionId: String)
|
||||
|
||||
/**
|
||||
* Creates an accept message suitable for this transport
|
||||
*/
|
||||
|
@ -59,13 +60,22 @@ internal interface VerificationTransport {
|
|||
fun createKey(tid: String,
|
||||
pubKey: String): VerificationInfoKey
|
||||
|
||||
fun createStart(fromDevice: String,
|
||||
method: String,
|
||||
transactionID: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoStart
|
||||
/**
|
||||
* Create start for SAS verification
|
||||
*/
|
||||
fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoStart
|
||||
|
||||
/**
|
||||
* Create start for QR code verification
|
||||
*/
|
||||
fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
sharedSecret: String): VerificationInfoStart
|
||||
|
||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||
|
||||
|
|
|
@ -16,14 +16,33 @@
|
|||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.work.*
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.Operation
|
||||
import androidx.work.WorkInfo
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.*
|
||||
import im.vector.matrix.android.api.session.room.model.message.*
|
||||
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.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationAcceptContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationCancelContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationDoneContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationKeyContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationMacContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
import im.vector.matrix.android.internal.di.DeviceId
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
|
@ -35,7 +54,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -245,24 +264,42 @@ internal class VerificationTransportRoomMessage(
|
|||
|
||||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = MessageVerificationMacContent.create(tid, mac, keys)
|
||||
|
||||
override fun createStart(fromDevice: String,
|
||||
method: String,
|
||||
transactionID: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoStart {
|
||||
override fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoStart {
|
||||
return MessageVerificationStartContent(
|
||||
fromDevice,
|
||||
hashes,
|
||||
keyAgreementProtocols,
|
||||
messageAuthenticationCodes,
|
||||
shortAuthenticationStrings,
|
||||
method,
|
||||
VERIFICATION_METHOD_SAS,
|
||||
RelationDefaultContent(
|
||||
type = RelationType.REFERENCE,
|
||||
eventId = transactionID
|
||||
)
|
||||
),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
sharedSecret: String): VerificationInfoStart {
|
||||
return MessageVerificationStartContent(
|
||||
fromDevice,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
VERIFICATION_METHOD_RECIPROCATE,
|
||||
RelationDefaultContent(
|
||||
type = RelationType.REFERENCE,
|
||||
eventId = transactionID
|
||||
),
|
||||
sharedSecret
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -292,7 +329,7 @@ internal class VerificationTransportRoomMessage(
|
|||
}
|
||||
}
|
||||
|
||||
internal class SasTransportRoomMessageFactory @Inject constructor(
|
||||
internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
||||
private val workManagerProvider: WorkManagerProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val monarchy: Monarchy,
|
||||
|
|
|
@ -21,7 +21,14 @@ import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
|||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.*
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.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.model.rest.KeyVerificationReady
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||
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
|
||||
|
@ -119,21 +126,35 @@ internal class VerificationTransportToDevice(
|
|||
|
||||
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = KeyVerificationMac.create(tid, mac, keys)
|
||||
|
||||
override fun createStart(fromDevice: String,
|
||||
method: String,
|
||||
transactionID: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoStart {
|
||||
override fun createStartForSas(fromDevice: String,
|
||||
transactionID: String,
|
||||
keyAgreementProtocols: List<String>,
|
||||
hashes: List<String>,
|
||||
messageAuthenticationCodes: List<String>,
|
||||
shortAuthenticationStrings: List<String>): VerificationInfoStart {
|
||||
return KeyVerificationStart(
|
||||
fromDevice,
|
||||
method,
|
||||
VERIFICATION_METHOD_SAS,
|
||||
transactionID,
|
||||
keyAgreementProtocols,
|
||||
hashes,
|
||||
messageAuthenticationCodes,
|
||||
shortAuthenticationStrings)
|
||||
shortAuthenticationStrings,
|
||||
null)
|
||||
}
|
||||
|
||||
override fun createStartForQrCode(fromDevice: String,
|
||||
transactionID: String,
|
||||
sharedSecret: String): VerificationInfoStart {
|
||||
return KeyVerificationStart(
|
||||
fromDevice,
|
||||
VERIFICATION_METHOD_RECIPROCATE,
|
||||
transactionID,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
sharedSecret)
|
||||
}
|
||||
|
||||
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
||||
|
@ -145,7 +166,7 @@ internal class VerificationTransportToDevice(
|
|||
}
|
||||
}
|
||||
|
||||
internal class SasTransportToDeviceFactory @Inject constructor(
|
||||
internal class VerificationTransportToDeviceFactory @Inject constructor(
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val taskExecutor: TaskExecutor) {
|
||||
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Copyright 2020 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.qrcode
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||
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.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
|
||||
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
|
||||
import im.vector.matrix.android.internal.util.withoutPrefix
|
||||
import timber.log.Timber
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
internal class DefaultQrCodeVerificationTransaction(
|
||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||
override val transactionId: String,
|
||||
override val otherUserId: String,
|
||||
override var otherDeviceId: String?,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
// Not null only if other user is able to scan QR code
|
||||
private val qrCodeData: QrCodeData?,
|
||||
val userId: String,
|
||||
val deviceId: String,
|
||||
override val isIncoming: Boolean
|
||||
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction {
|
||||
|
||||
override var cancelledReason: CancelCode? = null
|
||||
|
||||
override val qrCodeText: String?
|
||||
get() = qrCodeData?.toUrl()
|
||||
|
||||
override var state by Delegates.observable(VerificationTxState.None) { _, _, _ ->
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.transactionUpdated(this)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## Error while notifying listeners")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
|
||||
val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run {
|
||||
Timber.d("## Verification QR: Invalid QR Code Data")
|
||||
cancel(CancelCode.QrCodeInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
// Perform some checks
|
||||
if (otherQrCodeData.action != QrCodeData.ACTION_VERIFY) {
|
||||
Timber.d("## Verification QR: Invalid action ${otherQrCodeData.action}")
|
||||
cancel(CancelCode.QrCodeInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
if (otherQrCodeData.userId != otherUserId) {
|
||||
Timber.d("## Verification QR: Mismatched user ${otherQrCodeData.userId}")
|
||||
cancel(CancelCode.MismatchedUser)
|
||||
return
|
||||
}
|
||||
|
||||
if (otherQrCodeData.requestEventId != transactionId) {
|
||||
Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.requestEventId} expected:$transactionId")
|
||||
cancel(CancelCode.QrCodeInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
// check master key
|
||||
if (otherQrCodeData.otherUserKey != crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserKey}")
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
}
|
||||
|
||||
val toVerifyDeviceIds = mutableListOf<String>()
|
||||
var canTrustOtherUserMasterKey = false
|
||||
|
||||
val otherDevices = cryptoStore.getUserDevices(otherUserId)
|
||||
otherQrCodeData.keys.keys.forEach { key ->
|
||||
Timber.w("## Verification QR: Checking key $key")
|
||||
|
||||
when (val keyNoPrefix = key.withoutPrefix("ed25519:")) {
|
||||
otherQrCodeData.keys[key] -> {
|
||||
// Maybe master key?
|
||||
if (otherQrCodeData.keys[key] == crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) {
|
||||
canTrustOtherUserMasterKey = true
|
||||
} else {
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when (val otherDevice = otherDevices?.get(keyNoPrefix)) {
|
||||
null -> {
|
||||
// Unknown device, ignore
|
||||
}
|
||||
else -> {
|
||||
when (otherDevice.fingerprint()) {
|
||||
null -> {
|
||||
// Ignore
|
||||
}
|
||||
otherQrCodeData.keys[key] -> {
|
||||
// Store the deviceId to verify after
|
||||
toVerifyDeviceIds.add(key)
|
||||
}
|
||||
else -> {
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
|
||||
// Nothing to verify
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
return
|
||||
}
|
||||
|
||||
// All checks are correct
|
||||
// Send the shared secret so that sender can trust me
|
||||
// qrCodeData.sharedSecret will be used to send the start request
|
||||
start(otherQrCodeData.sharedSecret)
|
||||
|
||||
// Trust the other user
|
||||
trust(canTrustOtherUserMasterKey, toVerifyDeviceIds)
|
||||
}
|
||||
|
||||
fun start(remoteSecret: String) {
|
||||
if (state != VerificationTxState.None) {
|
||||
Timber.e("## Verification QR: start verification from invalid state")
|
||||
// should I cancel??
|
||||
throw IllegalStateException("Interactive Key verification already started")
|
||||
}
|
||||
|
||||
val startMessage = transport.createStartForQrCode(
|
||||
deviceId,
|
||||
transactionId,
|
||||
remoteSecret
|
||||
)
|
||||
|
||||
transport.sendToOther(
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
startMessage,
|
||||
VerificationTxState.Started,
|
||||
CancelCode.User,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
cancel(CancelCode.User)
|
||||
}
|
||||
|
||||
override fun cancel(code: CancelCode) {
|
||||
cancelledReason = code
|
||||
state = VerificationTxState.Cancelled
|
||||
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
|
||||
}
|
||||
|
||||
override fun isToDeviceTransport() = false
|
||||
|
||||
// Other user has scanned our QR code. check that the secret matched, so we can trust him
|
||||
fun onStartReceived(startReq: VerificationInfoStart) {
|
||||
if (qrCodeData == null) {
|
||||
// Should not happen
|
||||
cancel(CancelCode.UnexpectedMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if (startReq.sharedSecret == qrCodeData.sharedSecret) {
|
||||
// Ok, we can trust the other user
|
||||
// We can only trust the master key in this case
|
||||
trust(true, emptyList())
|
||||
} else {
|
||||
// Display a warning
|
||||
cancel(CancelCode.MismatchedKeys)
|
||||
}
|
||||
}
|
||||
|
||||
private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List<String>) {
|
||||
// If not me sign his MSK and upload the signature
|
||||
if (otherUserId != userId && canTrustOtherUserMasterKey) {
|
||||
// we should trust this master key
|
||||
// And check verification MSK -> SSK?
|
||||
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (otherUserId == userId) {
|
||||
// If me it's reasonable to sign and upload the device signature
|
||||
// Notice that i might not have the private keys, so may not be able to do it
|
||||
crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO what if the otherDevice is not in this list? and should we
|
||||
toVerifyDeviceIds.forEach {
|
||||
setDeviceVerified(otherUserId, it)
|
||||
}
|
||||
transport.done(transactionId)
|
||||
state = VerificationTxState.Verified
|
||||
}
|
||||
|
||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||
// TODO should not override cross sign status
|
||||
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
userId,
|
||||
deviceId)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
|
|||
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
|
||||
private const val ENCODING = "utf-8"
|
||||
|
||||
/**
|
||||
* Generate an URL to generate a QR code of the form:
|
||||
|
@ -28,27 +32,34 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
|||
* &key_<keyid>=<key-in-base64>...
|
||||
* &secret=<shared_secret>
|
||||
* &other_user_key=<master-key-in-base64>
|
||||
*
|
||||
* Example:
|
||||
* https://matrix.to/#/@user:matrix.org?
|
||||
* request=%24pBeIfm7REDACTEDSQJbgqvi-yYiwmPB8_H_W_O974
|
||||
* &action=verify
|
||||
* &key_VJEDVKUYTQ=DL7LWIw7Qp%2B4AREDACTEDOwy2BjygumSWAGfzaWY
|
||||
* &key_fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo=fsh%2FfQ08N3xvh4ySXsINB%2BJ2hREDACTEDVcVOG4qqo
|
||||
* &secret=AjQqw51Fp6UBuPolZ2FAD5WnXc22ZhJG6iGslrVvIdw%3D
|
||||
* &other_user_key=WqSVLkBCS%2Fi5NqR%2F%2FymC8T7K9RPxBIuqK8Usl6Y3big
|
||||
* </pre>
|
||||
*/
|
||||
fun QrCodeData.toUrl(): String {
|
||||
return buildString {
|
||||
append(PermalinkFactory.createPermalink(userId))
|
||||
append("?request=")
|
||||
append(PermalinkFactory.escape(requestEventId))
|
||||
append(URLEncoder.encode(requestEventId, ENCODING))
|
||||
append("&action=")
|
||||
append(PermalinkFactory.escape(action))
|
||||
append(URLEncoder.encode(action, ENCODING))
|
||||
|
||||
for ((keyId, key) in keys) {
|
||||
append("&key_")
|
||||
append(PermalinkFactory.escape(keyId))
|
||||
append("=")
|
||||
append(PermalinkFactory.escape(key))
|
||||
append("&key_$keyId=")
|
||||
append(URLEncoder.encode(key, ENCODING))
|
||||
}
|
||||
|
||||
append("&secret=")
|
||||
append(PermalinkFactory.escape(sharedSecret))
|
||||
append(URLEncoder.encode(sharedSecret, ENCODING))
|
||||
append("&other_user_key=")
|
||||
append(PermalinkFactory.escape(otherUserKey))
|
||||
append(URLEncoder.encode(otherUserKey, ENCODING))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +93,7 @@ fun String.toQrCodeData(): QrCodeData? {
|
|||
.filter { it.isNotEmpty() }
|
||||
|
||||
val keyValues = urlParams.map {
|
||||
(it.substringBefore("=") to it.substringAfter("=").let { value -> PermalinkFactory.unescape(value) })
|
||||
(it.substringBefore("=") to it.substringAfter("=").let { value -> URLDecoder.decode(value, ENCODING) })
|
||||
}.toMap()
|
||||
|
||||
val action = keyValues["action"] ?: return null
|
||||
|
|
|
@ -49,3 +49,5 @@ fun convertFromUTF8(s: String): String {
|
|||
s
|
||||
}
|
||||
}
|
||||
|
||||
fun String.withoutPrefix(prefix: String) = if (startsWith(prefix)) substringAfter(prefix) else this
|
||||
|
|
|
@ -39,7 +39,7 @@ class QrCodeTest {
|
|||
otherUserKey = "otherUserKey"
|
||||
)
|
||||
|
||||
private val basicUrl = "https://matrix.to/#/@benoit:matrix.org?request=\$azertyazerty&action=verify&key_1=abcdef&key_2=ghijql&secret=sharedSecret&other_user_key=otherUserKey"
|
||||
private val basicUrl = "https://matrix.to/#/@benoit:matrix.org?request=%24azertyazerty&action=verify&key_1=abcdef&key_2=ghijql&secret=sharedSecret&other_user_key=otherUserKey"
|
||||
|
||||
@Test
|
||||
fun testNominalCase() {
|
||||
|
@ -101,7 +101,7 @@ class QrCodeTest {
|
|||
|
||||
@Test
|
||||
fun testBadRequestEventId() {
|
||||
basicUrl.replace("\$azertyazerty", "@azertyazerty")
|
||||
basicUrl.replace("%24azertyazerty", "%32azertyazerty")
|
||||
.toQrCodeData()
|
||||
.shouldBeNull()
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import im.vector.riotx.R
|
|||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.qrcode.toQrCode
|
||||
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||
import im.vector.riotx.core.utils.allGranted
|
||||
|
@ -56,8 +55,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
|||
}
|
||||
|
||||
private fun renderQrCode(text: String) {
|
||||
val qrBitmap = text.toQrCode(200, 200)
|
||||
debug_qr_code.setImageBitmap(qrBitmap)
|
||||
debug_qr_code.setData(text, true)
|
||||
}
|
||||
|
||||
@OnClick(R.id.debug_test_text_view_link)
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="Scan QR-code" />
|
||||
|
||||
<ImageView
|
||||
<im.vector.riotx.core.ui.views.QrCodeImageView
|
||||
android:id="@+id/debug_qr_code"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
|
|
|
@ -23,22 +23,17 @@ import com.google.zxing.BarcodeFormat
|
|||
import com.google.zxing.common.BitMatrix
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
|
||||
fun String.toQrCode(width: Int,
|
||||
height: Int,
|
||||
@ColorInt backgroundColor: Int = Color.WHITE,
|
||||
@ColorInt foregroundColor: Int = Color.BLACK): Bitmap {
|
||||
fun String.toBitMatrix(size: Int): BitMatrix {
|
||||
return QRCodeWriter().encode(
|
||||
this,
|
||||
BarcodeFormat.QR_CODE,
|
||||
width,
|
||||
height
|
||||
).toBitmap(backgroundColor, foregroundColor)
|
||||
size,
|
||||
size
|
||||
)
|
||||
}
|
||||
|
||||
fun BitMatrix.toBitmap(@ColorInt backgroundColor: Int = Color.WHITE,
|
||||
@ColorInt foregroundColor: Int = Color.BLACK): Bitmap {
|
||||
val height: Int = height
|
||||
val width: Int = width
|
||||
val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2020 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.riotx.core.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.AnimationDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import im.vector.riotx.core.qrcode.toBitMatrix
|
||||
import im.vector.riotx.core.qrcode.toBitmap
|
||||
import kotlin.random.Random
|
||||
|
||||
class QrCodeImageView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
|
||||
private var data: String? = null
|
||||
private var animate = false
|
||||
|
||||
init {
|
||||
setBackgroundColor(Color.WHITE)
|
||||
}
|
||||
|
||||
fun setData(data: String, animate: Boolean) {
|
||||
this.data = data
|
||||
this.animate = animate
|
||||
|
||||
render()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
render()
|
||||
}
|
||||
|
||||
private fun render() {
|
||||
data
|
||||
?.takeIf { height > 0 }
|
||||
?.let {
|
||||
if (animate) {
|
||||
// NOT SUPPORTED YET val anim = createAnimation(it)
|
||||
// NOT SUPPORTED YET setImageDrawable(anim)
|
||||
// NOT SUPPORTED YET anim.start()
|
||||
// NOT SUPPORTED YET setImageDrawable(BitmapDrawable(resources, it.toBitMatrix(height).toBitmap()))
|
||||
val bitmap = it.toBitMatrix(height).toBitmap()
|
||||
post { setImageBitmap(bitmap) }
|
||||
} else {
|
||||
val bitmap = it.toBitMatrix(height).toBitmap()
|
||||
post { setImageBitmap(bitmap) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAnimation(data: String): AnimationDrawable {
|
||||
val finalQr = data.toBitMatrix(height)
|
||||
|
||||
val list = mutableListOf(finalQr)
|
||||
|
||||
val random = Random(System.currentTimeMillis())
|
||||
val repeatTime = 8
|
||||
repeat(repeatTime) { index ->
|
||||
val alteredQr = finalQr.clone()
|
||||
for (x in 0 until alteredQr.width) {
|
||||
for (y in 0 until alteredQr.height) {
|
||||
if (random.nextInt(repeatTime - index) == 0) {
|
||||
// Pb is that it does not toggle a whole black square, but only a pixel
|
||||
alteredQr.unset(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
list.add(alteredQr)
|
||||
}
|
||||
|
||||
val animDrawable = AnimationDrawable()
|
||||
|
||||
list.asReversed()
|
||||
.forEach {
|
||||
animDrawable.addFrame(BitmapDrawable(resources, it.toBitmap()), 150)
|
||||
}
|
||||
|
||||
return animDrawable
|
||||
}
|
||||
}
|
|
@ -64,12 +64,12 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
|||
|
||||
fun start(session: Session) {
|
||||
this.session = session
|
||||
session.getSasVerificationService().addListener(this)
|
||||
session.getVerificationService().addListener(this)
|
||||
session.addRoomKeysRequestListener(this)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
session?.getSasVerificationService()?.removeListener(this)
|
||||
session?.getVerificationService()?.removeListener(this)
|
||||
session?.removeRoomKeysRequestListener(this)
|
||||
session = null
|
||||
}
|
||||
|
|
|
@ -40,11 +40,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
|
|||
|
||||
fun start(session: Session) {
|
||||
this.session = session
|
||||
session.getSasVerificationService().addListener(this)
|
||||
session.getVerificationService().addListener(this)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
session?.getSasVerificationService()?.removeListener(this)
|
||||
session?.getVerificationService()?.removeListener(this)
|
||||
this.session = null
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
|
|||
}
|
||||
}
|
||||
dismissedAction = Runnable {
|
||||
session?.getSasVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
|
||||
session?.getVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
|
||||
pr.requestInfo?.fromDevice ?: "",
|
||||
pr.transactionId ?: "",
|
||||
pr.roomId ?: ""
|
||||
|
|
|
@ -19,10 +19,10 @@ package im.vector.riotx.features.crypto.verification
|
|||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class VerificationAction : VectorViewModelAction {
|
||||
data class RequestVerificationByDM(val userID: String, val roomId: String?) : VerificationAction()
|
||||
data class StartSASVerification(val userID: String, val pendingRequestTransactionId: String) : VerificationAction()
|
||||
data class RemoteQrCodeScanned(val userID: String, val transactionId: String, val scannedData: String) : VerificationAction()
|
||||
data class SASMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction()
|
||||
data class SASDoNotMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction()
|
||||
data class RequestVerificationByDM(val otherUserId: String, val roomId: String?) : VerificationAction()
|
||||
data class StartSASVerification(val otherUserId: String, val pendingRequestTransactionId: String) : VerificationAction()
|
||||
data class RemoteQrCodeScanned(val otherUserId: String, val transactionId: String, val scannedData: String) : VerificationAction()
|
||||
data class SASMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction()
|
||||
data class SASDoNotMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction()
|
||||
object GotItConclusion : VerificationAction()
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
it.otherUserMxItem?.let { matrixItem ->
|
||||
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
|
||||
|
||||
if (it.transactionState == VerificationTxState.Verified) {
|
||||
if (it.sasTransactionState == VerificationTxState.Verified || it.qrTransactionState == VerificationTxState.Verified) {
|
||||
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
|
||||
otherUserShield.isVisible = true
|
||||
} else {
|
||||
|
@ -108,8 +108,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
// Did the request result in a SAS transaction?
|
||||
if (it.transactionState != null) {
|
||||
when (it.transactionState) {
|
||||
if (it.sasTransactionState != null) {
|
||||
when (it.sasTransactionState) {
|
||||
VerificationTxState.None,
|
||||
VerificationTxState.SendingStart,
|
||||
VerificationTxState.Started,
|
||||
|
@ -138,7 +138,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
VerificationTxState.OnCancelled -> {
|
||||
showFragment(VerificationConclusionFragment::class, Bundle().apply {
|
||||
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
|
||||
it.transactionState == VerificationTxState.Verified,
|
||||
it.sasTransactionState == VerificationTxState.Verified,
|
||||
it.cancelCode?.value))
|
||||
})
|
||||
}
|
||||
|
@ -147,7 +147,21 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
return@withState
|
||||
}
|
||||
|
||||
// At this point there is no transaction for this request
|
||||
when (it.qrTransactionState) {
|
||||
VerificationTxState.Verified,
|
||||
VerificationTxState.Cancelled,
|
||||
VerificationTxState.OnCancelled -> {
|
||||
showFragment(VerificationConclusionFragment::class, Bundle().apply {
|
||||
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
|
||||
it.qrTransactionState == VerificationTxState.Verified,
|
||||
it.cancelCode?.value))
|
||||
})
|
||||
return@withState
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
// At this point there is no SAS transaction for this request
|
||||
|
||||
// Transaction has not yet started
|
||||
if (it.pendingRequest.invoke()?.cancelConclusion != null) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import com.squareup.inject.assisted.AssistedInject
|
|||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.sas.QRVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||
|
@ -46,13 +46,15 @@ import im.vector.riotx.core.di.HasScreenInjector
|
|||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import timber.log.Timber
|
||||
|
||||
data class VerificationBottomSheetViewState(
|
||||
val otherUserMxItem: MatrixItem? = null,
|
||||
val roomId: String? = null,
|
||||
val pendingRequest: Async<PendingVerificationRequest> = Uninitialized,
|
||||
val pendingLocalId: String? = null,
|
||||
val transactionState: VerificationTxState? = null,
|
||||
val sasTransactionState: VerificationTxState? = null,
|
||||
val qrTransactionState: VerificationTxState? = null,
|
||||
val transactionId: String? = null,
|
||||
val cancelCode: CancelCode? = null
|
||||
) : MvRxState
|
||||
|
@ -68,11 +70,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
get() = _requestLiveData
|
||||
|
||||
init {
|
||||
session.getSasVerificationService().addListener(this)
|
||||
session.getVerificationService().addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
session.getSasVerificationService().removeListener(this)
|
||||
session.getVerificationService().removeListener(this)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
|
@ -91,15 +93,20 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
|
||||
val userItem = session.getUser(args.otherUserId)
|
||||
|
||||
val pr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
|
||||
val pr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
|
||||
|
||||
val sasTx = (pr?.transactionId ?: args.verificationId)?.let {
|
||||
session.getSasVerificationService().getExistingTransaction(args.otherUserId, it)
|
||||
session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? SasVerificationTransaction
|
||||
}
|
||||
|
||||
val qrTx = (pr?.transactionId ?: args.verificationId)?.let {
|
||||
session.getVerificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction
|
||||
}
|
||||
|
||||
return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState(
|
||||
otherUserMxItem = userItem?.toMatrixItem(),
|
||||
transactionState = sasTx?.state,
|
||||
sasTransactionState = sasTx?.state,
|
||||
qrTransactionState = qrTx?.state,
|
||||
transactionId = args.verificationId,
|
||||
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
|
||||
roomId = args.roomId)
|
||||
|
@ -132,7 +139,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
copy(
|
||||
roomId = data,
|
||||
pendingRequest = Success(
|
||||
session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, data, pendingLocalId)
|
||||
session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, data, pendingLocalId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -146,16 +153,16 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
})
|
||||
} else {
|
||||
setState {
|
||||
copy(pendingRequest = Success(session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId)))
|
||||
copy(pendingRequest = Success(session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId)))
|
||||
}
|
||||
}
|
||||
}
|
||||
is VerificationAction.StartSASVerification -> {
|
||||
val request = session.getSasVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
|
||||
val request = session.getVerificationService().getExistingVerificationRequest(otherUserId, action.pendingRequestTransactionId)
|
||||
?: return@withState
|
||||
if (roomId == null) return@withState
|
||||
val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
|
||||
session.getSasVerificationService().beginKeyVerificationInDMs(
|
||||
session.getVerificationService().beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
transactionId = action.pendingRequestTransactionId,
|
||||
roomId = roomId,
|
||||
|
@ -165,20 +172,24 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
)
|
||||
}
|
||||
is VerificationAction.RemoteQrCodeScanned -> {
|
||||
// TODO Use session.getCrossSigningService()?
|
||||
val existingTransaction = session.getSasVerificationService()
|
||||
.getExistingTransaction(action.userID, action.transactionId) as? QRVerificationTransaction
|
||||
val existingTransaction = session.getVerificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.transactionId) as? QrCodeVerificationTransaction
|
||||
existingTransaction
|
||||
?.userHasScannedRemoteQrCode(action.scannedData)
|
||||
?.userHasScannedOtherQrCode(action.scannedData)
|
||||
?.let { cancelCode ->
|
||||
// Something went wrong
|
||||
Timber.w("## Something is not right: $cancelCode")
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
is VerificationAction.SASMatchAction -> {
|
||||
(session.getSasVerificationService()
|
||||
.getExistingTransaction(action.userID, action.sasTransactionId)
|
||||
(session.getVerificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||
as? SasVerificationTransaction)?.userHasVerifiedShortCode()
|
||||
}
|
||||
is VerificationAction.SASDoNotMatchAction -> {
|
||||
(session.getSasVerificationService()
|
||||
.getExistingTransaction(action.userID, action.sasTransactionId)
|
||||
(session.getVerificationService()
|
||||
.getExistingTransaction(action.otherUserId, action.sasTransactionId)
|
||||
as? SasVerificationTransaction)
|
||||
?.shortCodeDoesNotMatch()
|
||||
}
|
||||
|
@ -193,13 +204,28 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A SAS tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
transactionState = tx.state,
|
||||
cancelCode = tx.cancelledReason
|
||||
)
|
||||
when (tx) {
|
||||
is SasVerificationTransaction -> {
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A SAS tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
sasTransactionState = tx.state,
|
||||
cancelCode = tx.cancelledReason
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is QrCodeVerificationTransaction -> {
|
||||
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
|
||||
// A SAS tx has been started following this request
|
||||
setState {
|
||||
copy(
|
||||
qrTransactionState = tx.state,
|
||||
cancelCode = tx.cancelledReason
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,19 +19,16 @@ package im.vector.riotx.features.crypto.verification.choose
|
|||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.dividerItem
|
||||
import im.vector.riotx.core.qrcode.toQrCode
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationChooseMethodController @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val dimensionConverter: DimensionConverter
|
||||
private val colorProvider: ColorProvider
|
||||
) : EpoxyController() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
@ -52,14 +49,11 @@ class VerificationChooseMethodController @Inject constructor(
|
|||
notice(stringProvider.getString(R.string.verification_scan_notice))
|
||||
}
|
||||
|
||||
if (state.otherCanScanQrCode && !state.QRtext.isNullOrBlank()) {
|
||||
// Generate the QR code
|
||||
val size = dimensionConverter.dpToPx(180)
|
||||
val qrCodeBitmap = state.QRtext.toQrCode(size, size)
|
||||
|
||||
bottomSheetVerificationBigImageItem {
|
||||
if (state.otherCanScanQrCode && !state.qrCodeText.isNullOrBlank()) {
|
||||
bottomSheetVerificationQrCodeItem {
|
||||
id("qr")
|
||||
imageBitmap(qrCodeBitmap)
|
||||
data(state.qrCodeText)
|
||||
animate(false)
|
||||
}
|
||||
|
||||
dividerItem {
|
||||
|
|
|
@ -69,10 +69,10 @@ class VerificationChooseMethodFragment @Inject constructor(
|
|||
controller.update(state)
|
||||
}
|
||||
|
||||
override fun doVerifyBySas() = withState(sharedViewModel) {
|
||||
override fun doVerifyBySas() = withState(sharedViewModel) { state ->
|
||||
sharedViewModel.handle(VerificationAction.StartSASVerification(
|
||||
it.otherUserMxItem?.id ?: "",
|
||||
it.pendingRequest.invoke()?.transactionId ?: ""))
|
||||
state.otherUserMxItem?.id ?: "",
|
||||
state.pendingRequest.invoke()?.transactionId ?: ""))
|
||||
}
|
||||
|
||||
override fun openCamera() {
|
||||
|
@ -112,10 +112,10 @@ class VerificationChooseMethodFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) {
|
||||
private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) { state ->
|
||||
sharedViewModel.handle(VerificationAction.RemoteQrCodeScanned(
|
||||
it.otherUserMxItem?.id ?: "",
|
||||
it.pendingRequest.invoke()?.transactionId ?: "",
|
||||
state.otherUserMxItem?.id ?: "",
|
||||
state.pendingRequest.invoke()?.transactionId ?: "",
|
||||
remoteQrCode
|
||||
))
|
||||
}
|
||||
|
|
|
@ -22,8 +22,9 @@ import com.airbnb.mvrx.ViewModelContext
|
|||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
|
@ -37,7 +38,7 @@ data class VerificationChooseMethodViewState(
|
|||
val transactionId: String = "",
|
||||
val otherCanShowQrCode: Boolean = false,
|
||||
val otherCanScanQrCode: Boolean = false,
|
||||
val QRtext: String? = null,
|
||||
val qrCodeText: String? = null,
|
||||
val SASModeAvailable: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
|
@ -46,19 +47,27 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
|||
private val session: Session
|
||||
) : VectorViewModel<VerificationChooseMethodViewState, EmptyAction, EmptyViewEvents>(initialState), VerificationService.VerificationListener {
|
||||
|
||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
||||
override fun transactionCreated(tx: VerificationTransaction) {
|
||||
transactionUpdated(tx)
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {}
|
||||
override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
|
||||
if (tx.transactionId == state.transactionId && tx is QrCodeVerificationTransaction) {
|
||||
setState {
|
||||
copy(
|
||||
qrCodeText = tx.qrCodeText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
|
||||
val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId)
|
||||
val pvr = session.getVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
|
||||
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
|
||||
// TODO
|
||||
QRtext = "https://www.example.org",
|
||||
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
||||
)
|
||||
}
|
||||
|
@ -70,12 +79,12 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
session.getSasVerificationService().addListener(this)
|
||||
session.getVerificationService().addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
session.getSasVerificationService().removeListener(this)
|
||||
session.getVerificationService().removeListener(this)
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<VerificationChooseMethodViewModel, VerificationChooseMethodViewState> {
|
||||
|
@ -87,14 +96,16 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
|||
override fun initialState(viewModelContext: ViewModelContext): VerificationChooseMethodViewState? {
|
||||
val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args()
|
||||
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
|
||||
val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
|
||||
val pvr = session.getVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
|
||||
|
||||
// Get the QR code now, because transaction is already created, so transactionCreated() will not be called
|
||||
val qrCodeVerificationTransaction = session.getVerificationService().getExistingTransaction(args.otherUserId, args.verificationId ?: "")
|
||||
|
||||
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
|
||||
transactionId = args.verificationId ?: "",
|
||||
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
|
||||
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
|
||||
// TODO
|
||||
QRtext = "https://www.example.org",
|
||||
qrCodeText = (qrCodeVerificationTransaction as? QrCodeVerificationTransaction)?.qrCodeText,
|
||||
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
||||
)
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ class VerificationConclusionViewModel(initialState: VerificationConclusionViewSt
|
|||
val args = viewModelContext.args<VerificationConclusionFragment.Args>()
|
||||
|
||||
return when (safeValueOf(args.cancelReason)) {
|
||||
CancelCode.QrCodeInvalid,
|
||||
CancelCode.MismatchedUser,
|
||||
CancelCode.MismatchedSas,
|
||||
CancelCode.MismatchedCommitment,
|
||||
CancelCode.MismatchedKeys -> {
|
||||
|
|
|
@ -48,16 +48,16 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor(
|
|||
|
||||
init {
|
||||
withState { state ->
|
||||
refreshStateFromTx(session.getSasVerificationService()
|
||||
refreshStateFromTx(session.getVerificationService()
|
||||
.getExistingTransaction(state.otherUser?.id ?: "", state.transactionId
|
||||
?: "") as? SasVerificationTransaction)
|
||||
}
|
||||
|
||||
session.getSasVerificationService().addListener(this)
|
||||
session.getVerificationService().addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
session.getSasVerificationService().removeListener(this)
|
||||
session.getVerificationService().removeListener(this)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package im.vector.riotx.features.crypto.verification.epoxy
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
|
@ -34,18 +33,11 @@ abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel<BottomShee
|
|||
@EpoxyAttribute
|
||||
var imageRes: Int = 0
|
||||
|
||||
@EpoxyAttribute
|
||||
var imageBitmap: Bitmap? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var contentDescription: String? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
imageBitmap?.let {
|
||||
holder.image.setImageBitmap(it)
|
||||
} ?: run {
|
||||
holder.image.setImageResource(imageRes)
|
||||
}
|
||||
holder.image.setImageResource(imageRes)
|
||||
|
||||
if (contentDescription == null) {
|
||||
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2020 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.riotx.features.crypto.verification.epoxy
|
||||
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.ui.views.QrCodeImageView
|
||||
|
||||
/**
|
||||
* An Epoxy item displaying a QR code
|
||||
*/
|
||||
@EpoxyModelClass(layout = R.layout.item_verification_qr_code)
|
||||
abstract class BottomSheetVerificationQrCodeItem : VectorEpoxyModel<BottomSheetVerificationQrCodeItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var data: String
|
||||
|
||||
@EpoxyAttribute
|
||||
var animate = false
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.qsrCodeImage.setData(data, animate)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val qsrCodeImage by bind<QrCodeImageView>(R.id.itemVerificationQrCodeImage)
|
||||
}
|
||||
}
|
|
@ -411,7 +411,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
popDraft()
|
||||
}
|
||||
is ParsedCommand.VerifyUser -> {
|
||||
session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId)
|
||||
session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
|
@ -809,14 +809,19 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
|
||||
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
||||
Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}")
|
||||
if (session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId,
|
||||
if (session.getVerificationService().readyPendingVerificationInDMs(
|
||||
supportedVerificationMethods,
|
||||
action.otherUserId,
|
||||
room.roomId,
|
||||
action.transactionId)) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
||||
session.getSasVerificationService().declineVerificationRequestInDMs(
|
||||
session.getVerificationService().declineVerificationRequestInDMs(
|
||||
action.otherUserId,
|
||||
action.otherdDeviceId,
|
||||
action.transactionId,
|
||||
|
@ -830,7 +835,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
|
||||
private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) {
|
||||
// Check if this request is still active and handled by me
|
||||
session.getSasVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let {
|
||||
session.getVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let {
|
||||
if (it.handledByOtherSession) return
|
||||
if (!it.isFinished) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action.copy(
|
||||
|
|
|
@ -65,7 +65,7 @@ class DefaultNavigator @Inject constructor(
|
|||
|
||||
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransationId: String) {
|
||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
||||
val tx = session.getSasVerificationService().getExistingTransaction(otherUserId, sasTransationId) ?: return
|
||||
val tx = session.getVerificationService().getExistingTransaction(otherUserId, sasTransationId) ?: return
|
||||
(tx as? IncomingSasVerificationTransaction)?.performAccept()
|
||||
if (context is VectorBaseActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
|
|
|
@ -55,7 +55,7 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
is VerificationAction.StartSASVerification -> {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = action.userID,
|
||||
otherUserId = action.otherUserId,
|
||||
transactionId = action.pendingRequestTransactionId
|
||||
).show(requireActivity().supportFragmentManager, "REQPOP")
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
|
|||
}
|
||||
|
||||
fun manuallyVerify(device: CryptoDeviceInfo) {
|
||||
session.getSasVerificationService().beginKeyVerification(VerificationMethod.SAS, deviceID = device.deviceId, userId = userId)?.let { txID ->
|
||||
session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, device.deviceId)?.let { txID ->
|
||||
_requestLiveData.postValue(LiveEvent(Success(VerificationAction.StartSASVerification(userId, txID))))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -429,11 +429,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
// TODO Move to a ViewModel...
|
||||
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
refreshCryptographyPreference(data.devices ?: emptyList())
|
||||
if (isAdded) {
|
||||
refreshCryptographyPreference(data.devices ?: emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
refreshCryptographyPreference(emptyList())
|
||||
if (isAdded) {
|
||||
refreshCryptographyPreference(emptyList())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -86,11 +86,11 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
|||
|
||||
init {
|
||||
refreshDevicesList()
|
||||
session.getSasVerificationService().addListener(this)
|
||||
session.getVerificationService().addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
session.getSasVerificationService().removeListener(this)
|
||||
session.getVerificationService().removeListener(this)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
|||
|
||||
private fun handleVerify(action: DevicesAction.VerifyMyDevice) {
|
||||
// TODO Implement request in to DEVICE!!!
|
||||
val txID = session.getSasVerificationService().beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId)
|
||||
val txID = session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId)
|
||||
if (txID != null) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(
|
||||
action.copy(
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<im.vector.riotx.core.ui.views.QrCodeImageView
|
||||
android:id="@+id/itemVerificationQrCodeImage"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="@string/a11y_qr_code_for_verification"
|
||||
tools:src="@color/riotx_header_panel_background_black" />
|
||||
|
||||
</FrameLayout>
|
|
@ -100,7 +100,6 @@
|
|||
<string name="room_settings_enable_encryption_dialog_content">Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly.</string>
|
||||
<string name="room_settings_enable_encryption_dialog_submit">Enable encryption</string>
|
||||
|
||||
|
||||
<string name="verification_request_notice">For extra security, verify %s by checking a one-time code.</string>
|
||||
<string name="verification_request_start_notice">For maximum security, do this in person.</string>
|
||||
|
||||
|
@ -146,4 +145,6 @@
|
|||
|
||||
<string name="initialize_cross_signing">Initialize CrossSigning</string>
|
||||
<string name="reset_cross_signing">Reset Keys</string>
|
||||
|
||||
<string name="a11y_qr_code_for_verification">QR code</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue