Merge branch 'develop' into feature/verification_unified_emoji

This commit is contained in:
Valere 2020-04-10 16:05:33 +02:00 committed by GitHub
commit ccacd20428
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 358 additions and 107 deletions

View File

@ -14,6 +14,8 @@ Improvements 🙌:
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201) - Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
- Cross-Signing | Gossip key backup recovery key (#1200) - Cross-Signing | Gossip key backup recovery key (#1200)
- Show room encryption status as a bubble tile (#1078) - 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)
- Emoji Verification | It's not the same butterfly! (#1220) - Emoji Verification | It's not the same butterfly! (#1220)
Bugfix 🐛: Bugfix 🐛:

View File

@ -43,6 +43,7 @@ sealed class VerificationTxState {
// Will be used to ask the user if the other user has correctly scanned // Will be used to ask the user if the other user has correctly scanned
object QrScannedByOther : VerificationQrTxState() object QrScannedByOther : VerificationQrTxState()
object WaitingOtherReciprocateConfirm : VerificationQrTxState()
// Terminal states // Terminal states
abstract class TerminalTxState : VerificationTxState() abstract class TerminalTxState : VerificationTxState()

View File

@ -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) { private fun onRoomAcceptReceived(event: Event) {
@ -792,26 +790,53 @@ internal class DefaultVerificationService @Inject constructor(
private fun onDoneReceived(event: Event) { private fun onDoneReceived(event: Event) {
Timber.v("## onDoneReceived") Timber.v("## onDoneReceived")
val doneReq = event.getClearContent().toModel<KeyVerificationDone>()?.asValidObject() val doneReq = event.getClearContent().toModel<KeyVerificationDone>()?.asValidObject()
if (doneReq == null || event.senderId != userId) { if (doneReq == null || event.senderId == null) {
// ignore // ignore
Timber.e("## SAS Received invalid done request") Timber.e("## SAS Received invalid done request")
return return
} }
// We only send gossiping request when the other sent us a done handleDoneReceived(event.senderId, doneReq)
// We can ask without checking too much thinks (like trust), because we will check validity of secret on reception
getExistingTransaction(userId, doneReq.transactionId) if (event.senderId == userId) {
?: getOldTransaction(userId, doneReq.transactionId) // We only send gossiping request when the other sent us a done
?.let { vt -> // We can ask without checking too much thinks (like trust), because we will check validity of secret on reception
val otherDeviceId = vt.otherDeviceId getExistingTransaction(userId, doneReq.transactionId)
if (!crossSigningService.canCrossSign()) { ?: getOldTransaction(userId, doneReq.transactionId)
outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?.let { vt ->
?: "*"))) val otherDeviceId = vt.otherDeviceId
outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(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) { private fun onRoomDoneReceived(event: Event) {
@ -993,14 +1018,14 @@ internal class DefaultVerificationService @Inject constructor(
) )
} }
private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) { // private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) {
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId } // val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId }
if (existingRequest == null) { // if (existingRequest == null) {
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}") // Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}")
return // return
} // }
updatePendingRequest(existingRequest.copy(isSuccessful = true)) // updatePendingRequest(existingRequest.copy(isSuccessful = true))
} // }
// TODO All this methods should be delegated to a TransactionStore // TODO All this methods should be delegated to a TransactionStore
override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? { override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? {

View File

@ -57,7 +57,7 @@ internal abstract class DefaultVerificationTransaction(
protected fun trust(canTrustOtherUserMasterKey: Boolean, protected fun trust(canTrustOtherUserMasterKey: Boolean,
toVerifyDeviceIds: List<String>, toVerifyDeviceIds: List<String>,
eventuallyMarkMyMasterKeyAsTrusted: Boolean) { eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone : Boolean = true) {
Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds")
Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted")
@ -97,14 +97,9 @@ internal abstract class DefaultVerificationTransaction(
}) })
} }
state = VerificationTxState.Verified if (autoDone) {
state = VerificationTxState.Verified
transport.done(transactionId) { 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 ?: "*")))
// }
} }
} }

View File

@ -15,12 +15,12 @@
*/ */
package im.vector.matrix.android.internal.crypto.verification package im.vector.matrix.android.internal.crypto.verification
internal interface VerificationInfoDone : VerificationInfo<ValidVerificationInfoDone> { import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone
override fun asValidObject(): ValidVerificationInfoDone? { internal interface VerificationInfoDone : VerificationInfo<ValidVerificationDone> {
override fun asValidObject(): ValidVerificationDone? {
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
return ValidVerificationInfoDone(validTransactionId) return ValidVerificationDone(validTransactionId)
} }
} }
internal data class ValidVerificationInfoDone(val transactionId: String)

View File

@ -187,9 +187,12 @@ internal class DefaultQrCodeVerificationTransaction(
// qrCodeData.sharedSecret will be used to send the start request // qrCodeData.sharedSecret will be used to send the start request
start(otherQrCodeData.sharedSecret) start(otherQrCodeData.sharedSecret)
trust(canTrustOtherUserMasterKey, trust(
toVerifyDeviceIds.distinct(), canTrustOtherUserMasterKey = canTrustOtherUserMasterKey,
eventuallyMarkMyMasterKeyAsTrusted = true) toVerifyDeviceIds = toVerifyDeviceIds.distinct(),
eventuallyMarkMyMasterKeyAsTrusted = true,
autoDone = false
)
} }
private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) { private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) {
@ -199,6 +202,7 @@ internal class DefaultQrCodeVerificationTransaction(
throw IllegalStateException("Interactive Key verification already started") throw IllegalStateException("Interactive Key verification already started")
} }
state = VerificationTxState.Started
val startMessage = transport.createStartForQrCode( val startMessage = transport.createStartForQrCode(
deviceId, deviceId,
transactionId, transactionId,
@ -208,7 +212,7 @@ internal class DefaultQrCodeVerificationTransaction(
transport.sendToOther( transport.sendToOther(
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,
startMessage, startMessage,
VerificationTxState.Started, VerificationTxState.WaitingOtherReciprocateConfirm,
CancelCode.User, CancelCode.User,
onDone 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() { override fun otherUserScannedMyQrCode() {
when (qrCodeData) { when (qrCodeData) {
is QrCodeData.VerifyingAnotherUser -> { is QrCodeData.VerifyingAnotherUser -> {
@ -265,6 +278,6 @@ internal class DefaultQrCodeVerificationTransaction(
override fun otherUserDidNotScannedMyQrCode() { override fun otherUserDidNotScannedMyQrCode() {
// What can I do then? // What can I do then?
// At least remove the transaction... // At least remove the transaction...
state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true) cancel(CancelCode.MismatchedKeys)
} }
} }

View File

@ -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.choose.VerificationChooseMethodFragment
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment 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.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.qrconfirmation.VerificationQrScannedByOtherFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.grouplist.GroupListFragment import im.vector.riotx.features.grouplist.GroupListFragment
@ -339,6 +340,11 @@ interface FragmentModule {
@FragmentKey(VerificationQrScannedByOtherFragment::class) @FragmentKey(VerificationQrScannedByOtherFragment::class)
fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VerificationQRWaitingFragment::class)
fun bindVerificationQRWaitingFragment(fragment: VerificationQRWaitingFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(VerificationConclusionFragment::class) @FragmentKey(VerificationConclusionFragment::class)

View File

@ -16,6 +16,7 @@
package im.vector.riotx.features.crypto.quads package im.vector.riotx.features.crypto.quads
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
@ -34,9 +35,9 @@ import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.platform.WaitingViewData
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
data class SharedSecureStorageViewState( data class SharedSecureStorageViewState(
@ -77,7 +78,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) {
val decryptedSecretMap = HashMap<String, String>() val decryptedSecretMap = HashMap<String, String>()
GlobalScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
runCatching { runCatching {
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
val passphrase = action.passphrase val passphrase = action.passphrase
@ -116,14 +117,18 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
args.requestedSecrets.forEach { args.requestedSecrets.forEach {
val res = awaitCallback<String> { callback -> if (session.getAccountDataEvent(it) != null) {
session.sharedSecretStorageService.getSecret( val res = awaitCallback<String> { callback ->
name = it, session.sharedSecretStorageService.getSecret(
keyId = keyInfo.id, name = it,
secretKey = keySpec, keyId = keyInfo.id,
callback = callback) secretKey = keySpec,
callback = callback)
}
decryptedSecretMap[it] = res
} else {
Timber.w("## Cannot find secret $it in SSSS, skip")
} }
decryptedSecretMap[it] = res
} }
} }
}.fold({ }.fold({

View File

@ -32,6 +32,7 @@ import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.Session 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.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.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
@ -49,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.choose.VerificationChooseMethodFragment
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment 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.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.qrconfirmation.VerificationQrScannedByOtherFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
@ -108,7 +110,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
startActivityForResult(SharedSecureStorageActivity.newIntent( startActivityForResult(SharedSecureStorageActivity.newIntent(
requireContext(), requireContext(),
null, // use default key 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 SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
), SECRET_REQUEST_CODE) ), SECRET_REQUEST_CODE)
} }
@ -243,6 +245,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) showFragment(VerificationQrScannedByOtherFragment::class, Bundle())
return@withState 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 -> { is VerificationTxState.Verified -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.riotx.features.crypto.verification package im.vector.riotx.features.crypto.verification
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
@ -28,6 +29,7 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session 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.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.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
@ -46,8 +48,15 @@ import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem 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.fromBase64
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified 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.R
import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
data class VerificationBottomSheetViewState( data class VerificationBottomSheetViewState(
@ -71,7 +80,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: VerificationBottomSheetViewState, @Assisted initialState: VerificationBottomSheetViewState,
@Assisted val args: VerificationBottomSheet.VerificationArgs, @Assisted val args: VerificationBottomSheet.VerificationArgs,
private val session: Session, private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider) private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stringProvider: StringProvider)
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState), : VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
VerificationService.Listener { VerificationService.Listener {
@ -334,40 +344,82 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore) _viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
} }
is VerificationAction.GotResultFromSsss -> { is VerificationAction.GotResultFromSsss -> {
try { handleSecretBackFromSSSS(action)
action.cypherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<Map<String, String>>(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<Unit> {
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))
}
} }
}.exhaustive }.exhaustive
} }
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
try {
action.cypherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<Map<String, String>>(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<Unit> {
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(stringProvider.getString(R.string.error_failed_to_import_keys)))
}
// try the keybackup
tentativeRestoreBackup(res)
Unit
}
} catch (failure: Throwable) {
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage))
}
}
private fun tentativeRestoreBackup(res: Map<String, String>?) {
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<KeysVersionResult?> {
session.cryptoService().keysBackupService().getCurrentVersion(it)
} ?: return@launch
awaitCallback<ImportRoomKeysResult> {
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
version,
computeRecoveryKey(secret.fromBase64()),
null,
null,
null,
it
)
}
awaitCallback<Unit> {
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) { override fun transactionCreated(tx: VerificationTransaction) {
transactionUpdated(tx) transactionUpdated(tx)
} }

View File

@ -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))
}
}
}

View File

@ -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)
}
}

View File

@ -21,7 +21,9 @@ import im.vector.riotx.R
import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider 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.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.bottomSheetVerificationNoticeItem
import javax.inject.Inject import javax.inject.Inject
@ -32,33 +34,37 @@ class VerificationQrScannedByOtherController @Inject constructor(
var listener: Listener? = null var listener: Listener? = null
init { private var viewState: VerificationBottomSheetViewState? = null
fun update(viewState: VerificationBottomSheetViewState) {
this.viewState = viewState
requestModelBuild() requestModelBuild()
} }
override fun buildModels() { override fun buildModels() {
val state = viewState ?: return
bottomSheetVerificationNoticeItem { bottomSheetVerificationNoticeItem {
id("notice") id("notice")
notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice)) apply {
if (state.isMe) {
notice(stringProvider.getString(R.string.qr_code_scanned_self_verif_notice))
} else {
val name = state.otherUserMxItem?.getBestName() ?: ""
notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice, name))
}
}
}
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_trusted)
} }
dividerItem { dividerItem {
id("sep0") 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 { bottomSheetVerificationActionItem {
id("deny") id("deny")
title(stringProvider.getString(R.string.qr_code_scanned_by_other_no)) title(stringProvider.getString(R.string.qr_code_scanned_by_other_no))
@ -67,6 +73,19 @@ class VerificationQrScannedByOtherController @Inject constructor(
iconColor(colorProvider.getColor(R.color.vector_error_color)) iconColor(colorProvider.getColor(R.color.vector_error_color))
listener { listener?.onUserDeniesQrCodeScanned() } 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 { interface Listener {

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.crypto.verification.qrconfirmation
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
@ -37,10 +38,13 @@ class VerificationQrScannedByOtherFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupRecyclerView() setupRecyclerView()
} }
override fun invalidate() = withState(sharedViewModel) { state ->
controller.update(state)
}
override fun onDestroyView() { override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup() bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null controller.listener = null

View File

@ -2166,7 +2166,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada.</string>
<string name="a11y_qr_code_for_verification">QR kodea</string> <string name="a11y_qr_code_for_verification">QR kodea</string>
<string name="qr_code_scanned_by_other_notice">Beste erabiltzaileak QR kodea ongi eskaneatu du\?</string>
<string name="qr_code_scanned_by_other_yes">Bai</string> <string name="qr_code_scanned_by_other_yes">Bai</string>
<string name="qr_code_scanned_by_other_no">Ez</string> <string name="qr_code_scanned_by_other_no">Ez</string>

View File

@ -2174,7 +2174,6 @@ Si vous navez pas configuré de nouvelle méthode de récupération, un attaq
<string name="a11y_qr_code_for_verification">Code QR</string> <string name="a11y_qr_code_for_verification">Code QR</string>
<string name="qr_code_scanned_by_other_notice">Lautre utilisateur a-t-il bien scanné le code QR \?</string>
<string name="qr_code_scanned_by_other_yes">Oui</string> <string name="qr_code_scanned_by_other_yes">Oui</string>
<string name="qr_code_scanned_by_other_no">Non</string> <string name="qr_code_scanned_by_other_no">Non</string>

View File

@ -2169,7 +2169,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
<string name="a11y_qr_code_for_verification">QR kód</string> <string name="a11y_qr_code_for_verification">QR kód</string>
<string name="qr_code_scanned_by_other_notice">A másik felhasználó sikeresen beolvasta a QR kódot\?</string>
<string name="qr_code_scanned_by_other_yes">Igen</string> <string name="qr_code_scanned_by_other_yes">Igen</string>
<string name="qr_code_scanned_by_other_no">Nem</string> <string name="qr_code_scanned_by_other_no">Nem</string>

View File

@ -2219,7 +2219,6 @@
<string name="a11y_qr_code_for_verification">Codice QR</string> <string name="a11y_qr_code_for_verification">Codice QR</string>
<string name="qr_code_scanned_by_other_notice">L\'altro utente ha scansionato correttamente il codice QR\?</string>
<string name="qr_code_scanned_by_other_yes"></string> <string name="qr_code_scanned_by_other_yes"></string>
<string name="qr_code_scanned_by_other_no">No</string> <string name="qr_code_scanned_by_other_no">No</string>

View File

@ -2129,7 +2129,6 @@ Që të garantoni se sju shpëton gjë, thjesht mbajeni të aktivizuar mekani
<string name="initialize_cross_signing">Gatit <em>CrossSigning</em></string> <string name="initialize_cross_signing">Gatit <em>CrossSigning</em></string>
<string name="reset_cross_signing">Zeroji Kyçet</string> <string name="reset_cross_signing">Zeroji Kyçet</string>
<string name="qr_code_scanned_by_other_notice">A e skanoi me sukses përdoruesi tjetër kodin QR\?</string>
<string name="qr_code_scanned_by_other_no">Jo</string> <string name="qr_code_scanned_by_other_no">Jo</string>
<string name="no_connectivity_to_the_server_indicator">Humbi lidhja me shërbyesin</string> <string name="no_connectivity_to_the_server_indicator">Humbi lidhja me shërbyesin</string>

View File

@ -2119,7 +2119,6 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意
<string name="a11y_qr_code_for_verification">QR code</string> <string name="a11y_qr_code_for_verification">QR code</string>
<string name="qr_code_scanned_by_other_notice">其他使用者是否掃苗 QR code 成功?</string>
<string name="qr_code_scanned_by_other_yes"></string> <string name="qr_code_scanned_by_other_yes"></string>
<string name="qr_code_scanned_by_other_no"></string> <string name="qr_code_scanned_by_other_no"></string>

View File

@ -2148,7 +2148,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="a11y_qr_code_for_verification">QR code</string> <string name="a11y_qr_code_for_verification">QR code</string>
<string name="qr_code_scanned_by_other_notice">Did the other user successfully scan the QR code?</string> <string name="qr_code_scanned_by_other_notice">Almost there! Is %s showing the same shield?</string>
<string name="qr_code_scanned_by_other_yes">Yes</string> <string name="qr_code_scanned_by_other_yes">Yes</string>
<string name="qr_code_scanned_by_other_no">No</string> <string name="qr_code_scanned_by_other_no">No</string>

View File

@ -94,6 +94,13 @@
<string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string> <string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string>
<string name="room_created_summary_item">%s created and configured the room.</string> <string name="room_created_summary_item">%s created and configured the room.</string>
<string name="qr_code_scanned_self_verif_notice">Almost there! Is the other device showing the same shield?</string>
<string name="qr_code_scanned_verif_waiting_notice">Almost there! Waiting for confirmation…</string>
<string name="qr_code_scanned_verif_waiting">Waiting for %s…</string>
<string name="error_failed_to_import_keys">Failed to import keys</string>
<!-- END Strings added by Valere --> <!-- END Strings added by Valere -->