QR login + E2EE set up

This commit is contained in:
Hugh Nimmo-Smith 2022-10-13 21:15:52 +01:00
parent 0111b932de
commit 1ed082d3cb
2 changed files with 74 additions and 94 deletions

View File

@ -16,8 +16,11 @@
package org.matrix.android.sdk.internal.rendezvous package org.matrix.android.sdk.internal.rendezvous
import android.net.Uri
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
@ -56,9 +59,12 @@ internal data class Payload(
private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value
data class Rendezvous( /**
* Implementation of MSC3906 to sign in + E2EE set up using a QR code.
*/
class Rendezvous(
val channel: RendezvousChannel, val channel: RendezvousChannel,
val theirIntent: RendezvousIntent val theirIntent: RendezvousIntent,
) { ) {
companion object { companion object {
fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous { fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous {
@ -116,7 +122,7 @@ data class Rendezvous(
return checksum return checksum
} }
suspend fun completeOnNewDevice(): Session? { suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? {
Timber.tag(TAG).i("Waiting for login_token"); Timber.tag(TAG).i("Waiting for login_token");
val loginToken = receive() val loginToken = receive()
@ -143,22 +149,11 @@ data class Rendezvous(
Timber.tag(TAG).i("Got login_token: $login_token for $homeserver"); Timber.tag(TAG).i("Got login_token: $login_token for $homeserver");
// TODO: set view to be state logging in? val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver))
return authenticationService.loginUsingQrLoginToken(hsConfig, login_token)
}
// use token to login suspend fun completeVerificationOnNewDevice(session: Session) {
// const login = await sendLoginRequest(homeserver, undefined, "m.login.token", { token: login_token });
//
// await setLoggedIn(login);
//
// const { deviceId, userId } = login;
//
// const client = MatrixClientPeg.get();
//
val newSession: Session? = null
newSession ?.let {
session ->
val userId = session.myUserId val userId = session.myUserId
val crypto = session.cryptoService() val crypto = session.cryptoService()
val deviceId = crypto.getMyDevice().deviceId val deviceId = crypto.getMyDevice().deviceId
@ -170,32 +165,30 @@ data class Rendezvous(
val verificationResponse = receive() val verificationResponse = receive()
val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned")
val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId)
if (verifyingDeviceFromServer?.fingerprint() == verificationResponse.verifying_device_key) { if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifying_device_key) {
Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer")
return;
}
// set other device as verified // set other device as verified
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified");
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
verificationResponse.master_key ?.let { // TODO: what do we do with the master key?
// set master key as trusted // verificationResponse.master_key ?.let {
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) // // set master key as trusted
// crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it)
} // }
// request secrets from the verifying device // request secrets from the verifying device
Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId")
session.sharedSecretStorageService() .let { session.sharedSecretStorageService() .let {
it.requestSecret(verifyingDeviceId, MASTER_KEY_SSSS_NAME) it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId)
it.requestSecret(verifyingDeviceId, SELF_SIGNING_KEY_SSSS_NAME) it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
it.requestSecret(verifyingDeviceId, USER_SIGNING_KEY_SSSS_NAME) it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
it.requestSecret(verifyingDeviceId, KEYBACKUP_SECRET_SSSS_NAME) it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId)
} }
} else {
Timber.tag(TAG).i("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer")
}
}
return newSession
} }
private suspend fun receive(): Payload? { private suspend fun receive(): Payload? {

View File

@ -16,24 +16,31 @@
package im.vector.app.features.login.qr package im.vector.app.features.login.qr
import android.content.Context
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.configureAndStart
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.HomeActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.rendezvous.Rendezvous import org.matrix.android.sdk.internal.rendezvous.Rendezvous
import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason
import timber.log.Timber import timber.log.Timber
class QrCodeLoginViewModel @AssistedInject constructor( class QrCodeLoginViewModel @AssistedInject constructor(
@Assisted private val initialState: QrCodeLoginViewState @Assisted private val initialState: QrCodeLoginViewState,
private val applicationContext: Context,
private val authenticationService: AuthenticationService,
private val activeSessionHolder: ActiveSessionHolder,
) : VectorViewModel<QrCodeLoginViewState, QrCodeLoginAction, QrCodeLoginViewEvents>(initialState) { ) : VectorViewModel<QrCodeLoginViewState, QrCodeLoginAction, QrCodeLoginViewEvents>(initialState) {
val TAG: String = QrCodeLoginViewModel::class.java.simpleName val TAG: String = QrCodeLoginViewModel::class.java.simpleName
@AssistedFactory @AssistedFactory
@ -81,43 +88,30 @@ class QrCodeLoginViewModel @AssistedInject constructor(
_viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try {
val confirmationCode = rendezvous.startAfterScanningCode() val confirmationCode = rendezvous.startAfterScanningCode()
Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode")
confirmationCode ?.let { confirmationCode?.let {
onConnectionEstablished(it) onConnectionEstablished(it)
rendezvous.completeOnNewDevice() val session = rendezvous.waitForLoginOnNewDevice(authenticationService)
} onSigningIn()
} session?.let {
// if (isValidQrCode(action.qrCode)) { activeSessionHolder.setActiveSession(session)
// setState { authenticationService.reset()
// copy(
// connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice
// )
// }
// _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
// }
//
// // TODO. UI test purpose. Fixme remove! session.configureAndStart(applicationContext)
// viewModelScope.launch {
// delay(3000)
// onFailed(QrCodeLoginErrorType.TIMEOUT, true)
// delay(3000)
// onConnectionEstablished("1234-ABCD-5678-EFGH")
// delay(3000)
// onSigningIn()
// delay(3000)
// onFailed(QrCodeLoginErrorType.DEVICE_IS_NOT_SUPPORTED, false)
// }
// // TODO. UI test purpose. Fixme remove!
// viewModelScope.launch {
// delay(3000)
// onConnectionEstablished("1234-ABCD-5678-EFGH")
// delay(3000)
// onSigningIn()
// }
}
rendezvous.completeVerificationOnNewDevice(session)
_viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen)
}
}
} catch (failure: Throwable) {
Timber.tag(TAG).e(failure, "Error occurred during sign in")
onFailed(RendezvousFailureReason.Unknown)
}
}
}
private fun onFailed(reason: RendezvousFailureReason) { private fun onFailed(reason: RendezvousFailureReason) {
setState { setState {
@ -144,13 +138,6 @@ class QrCodeLoginViewModel @AssistedInject constructor(
} }
} }
/**
* TODO. UI test purpose. Fixme accordingly.
*/
private fun isValidQrCode(qrCode: String): Boolean {
return qrCode.startsWith("http")
}
/** /**
* TODO. UI test purpose. Fixme accordingly. * TODO. UI test purpose. Fixme accordingly.
*/ */