From 34dec64d9cd2d6fb8c34e596385d00a5b0484270 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 8 Apr 2020 17:55:47 +0200 Subject: [PATCH 1/8] Fixes #1214 --- CHANGES.md | 1 + .../quads/SharedSecureStorageViewModel.kt | 15 ++- .../verification/VerificationBottomSheet.kt | 3 +- .../VerificationBottomSheetViewModel.kt | 109 +++++++++++++----- 4 files changed, 92 insertions(+), 36 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 983c9841cd..174f4e3a6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ Improvements 🙌: - Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201) - Cross-Signing | Gossip key backup recovery key (#1200) - Show room encryption status as a bubble tile (#1078) + - Cross-Signing | Restore history after recover from passphrase (#1214) Bugfix 🐛: - Missing avatar/displayname after verification request message (#841) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt index a9f5d33888..3eef4b20cf 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import java.io.ByteArrayOutputStream data class SharedSecureStorageViewState( @@ -117,11 +118,15 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) + if (session.getAccountDataEvent(it) != null) { + session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec, + callback = callback) + } else { + Timber.w("## Cannot find secret $it in SSSS, skip") + } } decryptedSecretMap[it] = res } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index cd91cc3712..6b75c0147a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME @@ -108,7 +109,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { startActivityForResult(SharedSecureStorageActivity.newIntent( requireContext(), null, // use default key - listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME), + listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME), SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS ), SECRET_REQUEST_CODE) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index c37bba1469..71a9ec73e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.riotx.features.crypto.verification +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -28,6 +29,7 @@ import com.squareup.inject.assisted.Assisted 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.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME @@ -46,8 +48,13 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.crosssigning.isVerified +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.util.awaitCallback import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel +import kotlinx.coroutines.launch import timber.log.Timber data class VerificationBottomSheetViewState( @@ -334,40 +341,82 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( _viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore) } is VerificationAction.GotResultFromSsss -> { - try { - action.cypherData.fromBase64().inputStream().use { ins -> - val res = session.loadSecureSecret>(ins, action.alias) - val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( - res?.get(MASTER_KEY_SSSS_NAME), - res?.get(USER_SIGNING_KEY_SSSS_NAME), - res?.get(SELF_SIGNING_KEY_SSSS_NAME) - ) - if (trustResult.isVerified()) { - // Sign this device and upload the signature - session.sessionParams.credentials.deviceId?.let { deviceId -> - session.cryptoService() - .crossSigningService().trustDevice(deviceId, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.w(failure, "Failed to sign my device after recovery") - } - }) - } - - setState { - copy(verifiedFromPrivateKeys = true) - } - } else { - // POP UP something - _viewEvents.post(VerificationBottomSheetViewEvents.ModalError("Failed to import keys")) - } - } - } catch (failure: Throwable) { - _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage)) - } + handleSecretBackFromSSSS(action) } }.exhaustive } + private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) { + try { + action.cypherData.fromBase64().inputStream().use { ins -> + val res = session.loadSecureSecret>(ins, action.alias) + val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( + res?.get(MASTER_KEY_SSSS_NAME), + res?.get(USER_SIGNING_KEY_SSSS_NAME), + res?.get(SELF_SIGNING_KEY_SSSS_NAME) + ) + if (trustResult.isVerified()) { + // Sign this device and upload the signature + session.sessionParams.credentials.deviceId?.let { deviceId -> + session.cryptoService() + .crossSigningService().trustDevice(deviceId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.w(failure, "Failed to sign my device after recovery") + } + }) + } + + setState { + copy(verifiedFromPrivateKeys = true) + } + + // try to get keybackup key + } else { + // POP UP something + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError("Failed to import keys")) + } + + // try the keybackup + tentativeRestoreBackup(res) + Unit + } + } catch (failure: Throwable) { + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage)) + } + } + + private fun tentativeRestoreBackup(res: Map?) { + viewModelScope.launch { + try { + val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { + Timber.v("## Keybackup secret not restored from SSSS") + } + + val version = awaitCallback { + session.cryptoService().keysBackupService().getCurrentVersion(it) + } ?: return@launch + + awaitCallback { + session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + version, + computeRecoveryKey(secret.fromBase64()), + null, + null, + null, + it + ) + } + + awaitCallback { + session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it) + } + } catch (failure: Throwable) { + // Just ignore for now + Timber.v("## Failed to restore backup after SSSS recovery") + } + } + } + override fun transactionCreated(tx: VerificationTransaction) { transactionUpdated(tx) } From 7c0137e2dc8e42e0690b0ce2ae144d5b7f7faa60 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Apr 2020 10:46:52 +0200 Subject: [PATCH 2/8] Fix / await callback suspend forever --- .../crypto/quads/SharedSecureStorageViewModel.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt index 3eef4b20cf..9cd60eba43 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.crypto.quads +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory @@ -34,7 +35,6 @@ import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.resources.StringProvider import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber @@ -78,7 +78,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { val decryptedSecretMap = HashMap() - GlobalScope.launch(Dispatchers.IO) { + viewModelScope.launch(Dispatchers.IO) { runCatching { _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) val passphrase = action.passphrase @@ -117,18 +117,18 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { - val res = awaitCallback { callback -> - if (session.getAccountDataEvent(it) != null) { + if (session.getAccountDataEvent(it) != null) { + val res = awaitCallback { callback -> session.sharedSecretStorageService.getSecret( name = it, keyId = keyInfo.id, secretKey = keySpec, callback = callback) - } else { - Timber.w("## Cannot find secret $it in SSSS, skip") } + decryptedSecretMap[it] = res + } else { + Timber.w("## Cannot find secret $it in SSSS, skip") } - decryptedSecretMap[it] = res } } }.fold({ From f5dc0b38ff5aa2a086dff0a384d34f518fca07dd Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Apr 2020 11:47:54 +0200 Subject: [PATCH 3/8] Code review --- .../verification/VerificationBottomSheetViewModel.kt | 7 +++++-- vector/src/main/res/values/strings_riotX.xml | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 71a9ec73e7..c7a5b3a011 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -52,8 +52,10 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersio import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.util.awaitCallback +import im.vector.riotx.R import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.resources.StringProvider import kotlinx.coroutines.launch import timber.log.Timber @@ -78,7 +80,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: VerificationBottomSheetViewState, @Assisted val args: VerificationBottomSheet.VerificationArgs, private val session: Session, - private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider) + private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, + private val stringProvider: StringProvider) : VectorViewModel(initialState), VerificationService.Listener { @@ -373,7 +376,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( // try to get keybackup key } else { // POP UP something - _viewEvents.post(VerificationBottomSheetViewEvents.ModalError("Failed to import keys")) + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys))) } // try the keybackup diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 606a1b95f6..a3f57b7716 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -94,6 +94,8 @@ The encryption used by this room is not supported %s created and configured the room. + Failed to import keys + From 5081361c2d17fa3a46b3f455b65a5c25dff58641 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Apr 2020 16:29:52 +0200 Subject: [PATCH 4/8] Update Scan confirm flow --- .../verification/VerificationTxState.kt | 1 + .../DefaultVerificationService.kt | 73 +++++++++++++------ .../DefaultVerificationTransaction.kt | 13 +--- .../verification/VerificationInfoDone.kt | 10 +-- .../DefaultQrCodeVerificationTransaction.kt | 23 ++++-- .../im/vector/riotx/core/di/FragmentModule.kt | 6 ++ .../verification/VerificationBottomSheet.kt | 8 ++ .../VerificationQRWaitingController.kt | 60 +++++++++++++++ .../VerificationQRWaitingFragment.kt | 59 +++++++++++++++ .../VerificationQrScannedByOtherController.kt | 17 ++++- .../VerificationQrScannedByOtherFragment.kt | 6 +- vector/src/main/res/values/strings.xml | 2 +- vector/src/main/res/values/strings_riotX.xml | 5 ++ 13 files changed, 236 insertions(+), 47 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt index aaaf227187..868ec5a3e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt @@ -43,6 +43,7 @@ sealed class VerificationTxState { // Will be used to ask the user if the other user has correctly scanned object QrScannedByOther : VerificationQrTxState() + object WaitingOtherReciprocateConfirm : VerificationQrTxState() // Terminal states abstract class TerminalTxState : VerificationTxState() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index db6c224019..77dcc483bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -646,9 +646,7 @@ internal class DefaultVerificationService @Inject constructor( )) } - if (existingTransaction is SASDefaultVerificationTransaction) { - existingTransaction.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false) - } + existingTransaction?.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false) } private fun onRoomAcceptReceived(event: Event) { @@ -792,26 +790,53 @@ internal class DefaultVerificationService @Inject constructor( private fun onDoneReceived(event: Event) { Timber.v("## onDoneReceived") val doneReq = event.getClearContent().toModel()?.asValidObject() - if (doneReq == null || event.senderId != userId) { + if (doneReq == null || event.senderId == null) { // ignore Timber.e("## SAS Received invalid done request") return } - // We only send gossiping request when the other sent us a done - // We can ask without checking too much thinks (like trust), because we will check validity of secret on reception - getExistingTransaction(userId, doneReq.transactionId) - ?: getOldTransaction(userId, doneReq.transactionId) - ?.let { vt -> - val otherDeviceId = vt.otherDeviceId - if (!crossSigningService.canCrossSign()) { - outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId - ?: "*"))) - outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + handleDoneReceived(event.senderId, doneReq) + + if (event.senderId == userId) { + // We only send gossiping request when the other sent us a done + // We can ask without checking too much thinks (like trust), because we will check validity of secret on reception + getExistingTransaction(userId, doneReq.transactionId) + ?: getOldTransaction(userId, doneReq.transactionId) + ?.let { vt -> + val otherDeviceId = vt.otherDeviceId + if (!crossSigningService.canCrossSign()) { + outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + ?: "*"))) + outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + ?: "*"))) + } + outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) } - outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - } + } + } + + private fun handleDoneReceived(senderId: String, doneReq: ValidVerificationDone) { + Timber.v("## SAS Done receieved $doneReq") + val existing = getExistingTransaction(senderId, doneReq.transactionId) + if (existing == null) { + Timber.e("## SAS Received invalid Done request") + return + } + if (existing is DefaultQrCodeVerificationTransaction) { + existing.onDoneReceived() + } else { + // SAS do not care for now? + } + + // Now transactions are udated, let's also update Requests + val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneReq.transactionId } + if (existingRequest == null) { + Timber.e("## SAS Received Done for unknown request txId:${doneReq.transactionId}") + return + } + updatePendingRequest(existingRequest.copy(isSuccessful = true)) } private fun onRoomDoneReceived(event: Event) { @@ -993,14 +1018,14 @@ internal class DefaultVerificationService @Inject constructor( ) } - private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) { - val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId } - if (existingRequest == null) { - Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}") - return - } - updatePendingRequest(existingRequest.copy(isSuccessful = true)) - } +// private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) { +// val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId } +// if (existingRequest == null) { +// Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}") +// return +// } +// updatePendingRequest(existingRequest.copy(isSuccessful = true)) +// } // TODO All this methods should be delegated to a TransactionStore override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index 29cfcd2383..eb78aee42d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -57,7 +57,7 @@ internal abstract class DefaultVerificationTransaction( protected fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List, - eventuallyMarkMyMasterKeyAsTrusted: Boolean) { + eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone : Boolean = true) { Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") @@ -97,14 +97,9 @@ internal abstract class DefaultVerificationTransaction( }) } - state = VerificationTxState.Verified - - transport.done(transactionId) { -// if (otherUserId == userId && !crossSigningService.canCrossSign()) { -// outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) -// outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) -// outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) -// } + if (autoDone) { + state = VerificationTxState.Verified + transport.done(transactionId) {} } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt index 2986013fca..8cf96d7d65 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt @@ -15,12 +15,12 @@ */ package im.vector.matrix.android.internal.crypto.verification -internal interface VerificationInfoDone : VerificationInfo { +import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone - override fun asValidObject(): ValidVerificationInfoDone? { +internal interface VerificationInfoDone : VerificationInfo { + + override fun asValidObject(): ValidVerificationDone? { val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null - return ValidVerificationInfoDone(validTransactionId) + return ValidVerificationDone(validTransactionId) } } - -internal data class ValidVerificationInfoDone(val transactionId: String) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 41d8ce7f44..59ee23cc62 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -187,9 +187,12 @@ internal class DefaultQrCodeVerificationTransaction( // qrCodeData.sharedSecret will be used to send the start request start(otherQrCodeData.sharedSecret) - trust(canTrustOtherUserMasterKey, - toVerifyDeviceIds.distinct(), - eventuallyMarkMyMasterKeyAsTrusted = true) + trust( + canTrustOtherUserMasterKey = canTrustOtherUserMasterKey, + toVerifyDeviceIds = toVerifyDeviceIds.distinct(), + eventuallyMarkMyMasterKeyAsTrusted = true, + autoDone = false + ) } private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) { @@ -199,6 +202,7 @@ internal class DefaultQrCodeVerificationTransaction( throw IllegalStateException("Interactive Key verification already started") } + state = VerificationTxState.Started val startMessage = transport.createStartForQrCode( deviceId, transactionId, @@ -208,7 +212,7 @@ internal class DefaultQrCodeVerificationTransaction( transport.sendToOther( EventType.KEY_VERIFICATION_START, startMessage, - VerificationTxState.Started, + VerificationTxState.WaitingOtherReciprocateConfirm, CancelCode.User, onDone ) @@ -244,6 +248,15 @@ internal class DefaultQrCodeVerificationTransaction( } } + fun onDoneReceived() { + if (state != VerificationTxState.WaitingOtherReciprocateConfirm) { + cancel(CancelCode.UnexpectedMessage) + return + } + state = VerificationTxState.Verified + transport.done(transactionId) {} + } + override fun otherUserScannedMyQrCode() { when (qrCodeData) { is QrCodeData.VerifyingAnotherUser -> { @@ -265,6 +278,6 @@ internal class DefaultQrCodeVerificationTransaction( override fun otherUserDidNotScannedMyQrCode() { // What can I do then? // At least remove the transaction... - state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true) + cancel(CancelCode.MismatchedKeys) } } diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 9f3cdba683..c68972cdd4 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -37,6 +37,7 @@ import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFrag import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment +import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.grouplist.GroupListFragment @@ -339,6 +340,11 @@ interface FragmentModule { @FragmentKey(VerificationQrScannedByOtherFragment::class) fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment + @Binds + @IntoMap + @FragmentKey(VerificationQRWaitingFragment::class) + fun bindVerificationQRWaitingFragment(fragment: VerificationQRWaitingFragment): Fragment + @Binds @IntoMap @FragmentKey(VerificationConclusionFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 6b75c0147a..695716d386 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -50,6 +50,7 @@ import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFrag import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment +import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.home.AvatarRenderer @@ -244,6 +245,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) return@withState } + is VerificationTxState.Started, + is VerificationTxState.WaitingOtherReciprocateConfirm -> { + showFragment(VerificationQRWaitingFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationQRWaitingFragment.Args(state.isMe, state.otherUserMxItem?.getBestName() ?: "")) + }) + return@withState + } is VerificationTxState.Verified -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt new file mode 100644 index 0000000000..2214774882 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 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.qrconfirmation + +import com.airbnb.epoxy.EpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +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.bottomSheetVerificationWaitingItem +import javax.inject.Inject + +class VerificationQRWaitingController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + private var args: VerificationQRWaitingFragment.Args? = null + + fun update(args: VerificationQRWaitingFragment.Args) { + this.args = args + requestModelBuild() + } + + override fun buildModels() { + val params = args ?: return + + bottomSheetVerificationNoticeItem { + id("notice") + apply { + notice(stringProvider.getString(R.string.qr_code_scanned_verif_waiting_notice)) + } + } + + bottomSheetVerificationBigImageItem { + id("image") + imageRes(R.drawable.ic_shield_trusted) + } + + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.qr_code_scanned_verif_waiting, params.otherUserName)) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt new file mode 100644 index 0000000000..77de3997cb --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 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.qrconfirmation + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.MvRx +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationQRWaitingFragment @Inject constructor( + val controller: VerificationQRWaitingController +) : VectorBaseFragment() { + + @Parcelize + data class Args( + val isMe: Boolean, + val otherUserName: String + ) : Parcelable + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + (arguments?.getParcelable(MvRx.KEY_ARG) as? Args)?.let { + controller.update(it) + } + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt index f775ac7941..6271559d31 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt @@ -21,6 +21,7 @@ import im.vector.riotx.R import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import javax.inject.Inject @@ -32,14 +33,26 @@ class VerificationQrScannedByOtherController @Inject constructor( var listener: Listener? = null - init { + private var viewState: VerificationBottomSheetViewState? = null + + fun update(viewState: VerificationBottomSheetViewState) { + this.viewState = viewState requestModelBuild() } override fun buildModels() { + val state = viewState ?: return + bottomSheetVerificationNoticeItem { id("notice") - notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice)) + apply { + if (state.isMe) { + val name = state.otherUserMxItem?.getBestName() ?: "" + notice(stringProvider.getString(R.string.qr_code_scanned_self_verif_notice, name)) + } else { + notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice)) + } + } } dividerItem { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt index 14d294a27a..a8a16f8006 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.crypto.verification.qrconfirmation import android.os.Bundle import android.view.View import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith @@ -37,10 +38,13 @@ class VerificationQrScannedByOtherFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupRecyclerView() } + override fun invalidate() = withState(sharedViewModel) { state -> + controller.update(state) + } + override fun onDestroyView() { bottomSheetVerificationRecyclerView.cleanup() controller.listener = null diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 928aafbd55..ae2cb7bbbb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2148,7 +2148,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming QR code - Did the other user successfully scan the QR code? + Almost there! Is %s showing the same shield? Yes No diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index a3f57b7716..9ef21170a4 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -94,6 +94,11 @@ The encryption used by this room is not supported %s created and configured the room. + + Almost there! Is the other device showing the same shield? + Almost there! Waiting for confirmation… + Waiting for %s… + Failed to import keys From 68323057aad43872952232bc02db04ad31f8c216 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Apr 2020 16:34:30 +0200 Subject: [PATCH 5/8] Update change log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 174f4e3a6f..0df276fc2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Improvements 🙌: - Cross-Signing | Gossip key backup recovery key (#1200) - Show room encryption status as a bubble tile (#1078) - Cross-Signing | Restore history after recover from passphrase (#1214) + - Cross-Sign | QR code scan confirmation screens design update (#1187) Bugfix 🐛: - Missing avatar/displayname after verification request message (#841) From fccfd00949393b33b8cac2fa52a2518c7d2fa5cc Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Apr 2020 17:04:24 +0200 Subject: [PATCH 6/8] Fix / design update --- .../VerificationQrScannedByOtherController.kt | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt index 6271559d31..c7a086a423 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt @@ -23,6 +23,7 @@ import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState 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 javax.inject.Inject @@ -55,23 +56,15 @@ class VerificationQrScannedByOtherController @Inject constructor( } } + bottomSheetVerificationBigImageItem { + id("image") + imageRes(R.drawable.ic_shield_trusted) + } + dividerItem { id("sep0") } - bottomSheetVerificationActionItem { - id("confirm") - title(stringProvider.getString(R.string.qr_code_scanned_by_other_yes)) - titleColor(colorProvider.getColor(R.color.riotx_accent)) - iconRes(R.drawable.ic_check_on) - iconColor(colorProvider.getColor(R.color.riotx_accent)) - listener { listener?.onUserConfirmsQrCodeScanned() } - } - - dividerItem { - id("sep1") - } - bottomSheetVerificationActionItem { id("deny") title(stringProvider.getString(R.string.qr_code_scanned_by_other_no)) @@ -80,6 +73,19 @@ class VerificationQrScannedByOtherController @Inject constructor( iconColor(colorProvider.getColor(R.color.vector_error_color)) listener { listener?.onUserDeniesQrCodeScanned() } } + + dividerItem { + id("sep1") + } + + bottomSheetVerificationActionItem { + id("confirm") + title(stringProvider.getString(R.string.qr_code_scanned_by_other_yes)) + titleColor(colorProvider.getColor(R.color.riotx_accent)) + iconRes(R.drawable.ic_check_on) + iconColor(colorProvider.getColor(R.color.riotx_accent)) + listener { listener?.onUserConfirmsQrCodeScanned() } + } } interface Listener { From 943ba3bebdce0c86ff3579960c361fc9e0cc3972 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Apr 2020 13:14:12 +0200 Subject: [PATCH 7/8] Fix / string bad argument number - lint --- .../VerificationQrScannedByOtherController.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt index c7a086a423..dd1d3d0f90 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt @@ -48,10 +48,10 @@ class VerificationQrScannedByOtherController @Inject constructor( id("notice") apply { if (state.isMe) { - val name = state.otherUserMxItem?.getBestName() ?: "" - notice(stringProvider.getString(R.string.qr_code_scanned_self_verif_notice, name)) + notice(stringProvider.getString(R.string.qr_code_scanned_self_verif_notice)) } else { - notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice)) + val name = state.otherUserMxItem?.getBestName() ?: "" + notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice, name)) } } } From 9cfb83f0d2cff846e78868c1b6908b2a1766348c Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Apr 2020 14:31:03 +0200 Subject: [PATCH 8/8] Remove outdated translation --- vector/src/main/res/values-eu/strings.xml | 1 - vector/src/main/res/values-fr/strings.xml | 1 - vector/src/main/res/values-hu/strings.xml | 1 - vector/src/main/res/values-it/strings.xml | 1 - vector/src/main/res/values-sq/strings.xml | 1 - vector/src/main/res/values-zh-rTW/strings.xml | 1 - 6 files changed, 6 deletions(-) diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 86fb1e0e12..dd8ead5f27 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2166,7 +2166,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. QR kodea - Beste erabiltzaileak QR kodea ongi eskaneatu du\? Bai Ez diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 341a247d52..76e2a0d0a7 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2174,7 +2174,6 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Code QR - L’autre utilisateur a-t-il bien scanné le code QR \? Oui Non diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 64eee79075..c4dde2e079 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -2169,7 +2169,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró QR kód - A másik felhasználó sikeresen beolvasta a QR kódot\? Igen Nem diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 4d1ac8edaf..92b235c5dd 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2219,7 +2219,6 @@ Codice QR - L\'altro utente ha scansionato correttamente il codice QR\? No diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 7ff5253530..96e0e1201a 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2129,7 +2129,6 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Gatit CrossSigning Zeroji Kyçet - A e skanoi me sukses përdoruesi tjetër kodin QR\? Jo Humbi lidhja me shërbyesin diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 8078ca8efa..111ee26b36 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2119,7 +2119,6 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 QR code - 其他使用者是否掃苗 QR code 成功?