From 1ed082d3cb16943a15664dbb862126942f5539d3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:15:52 +0100 Subject: [PATCH] QR login + E2EE set up --- .../sdk/internal/rendezvous/Rendezvous.kt | 95 +++++++++---------- .../features/login/qr/QrCodeLoginViewModel.kt | 73 ++++++-------- 2 files changed, 74 insertions(+), 94 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt index dd7b91582b..2f85a97c55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt @@ -16,8 +16,11 @@ package org.matrix.android.sdk.internal.rendezvous +import android.net.Uri import com.squareup.moshi.Json 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.session.Session 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 -data class Rendezvous( +/** + * Implementation of MSC3906 to sign in + E2EE set up using a QR code. + */ +class Rendezvous( val channel: RendezvousChannel, - val theirIntent: RendezvousIntent + val theirIntent: RendezvousIntent, ) { companion object { fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous { @@ -116,7 +122,7 @@ data class Rendezvous( return checksum } - suspend fun completeOnNewDevice(): Session? { + suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? { Timber.tag(TAG).i("Waiting for login_token"); val loginToken = receive() @@ -143,59 +149,46 @@ data class Rendezvous( 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 -// const login = await sendLoginRequest(homeserver, undefined, "m.login.token", { token: login_token }); -// -// await setLoggedIn(login); -// -// const { deviceId, userId } = login; -// -// const client = MatrixClientPeg.get(); -// + suspend fun completeVerificationOnNewDevice(session: Session) { + val userId = session.myUserId + val crypto = session.cryptoService() + val deviceId = crypto.getMyDevice().deviceId + val deviceKey = crypto.getMyDevice().fingerprint() + send(Payload(PayloadType.Progress, outcome = "success", device_id = deviceId, device_key = deviceKey)) - val newSession: Session? = null + // await confirmation of verification - newSession ?.let { - session -> - val userId = session.myUserId - val crypto = session.cryptoService() - val deviceId = crypto.getMyDevice().deviceId - val deviceKey = crypto.getMyDevice().fingerprint() - send(Payload(PayloadType.Progress, outcome = "success", device_id = deviceId, device_key = deviceKey)) - - // await confirmation of verification - - val verificationResponse = receive() - val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") - val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) - if (verifyingDeviceFromServer?.fingerprint() == verificationResponse.verifying_device_key) { - // set other device as verified - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - - verificationResponse.master_key ?.let { - // set master key as trusted - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) - - } - - // request secrets from the verifying device - Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") - - session.sharedSecretStorageService() .let { - it.requestSecret(verifyingDeviceId, MASTER_KEY_SSSS_NAME) - it.requestSecret(verifyingDeviceId, SELF_SIGNING_KEY_SSSS_NAME) - it.requestSecret(verifyingDeviceId, USER_SIGNING_KEY_SSSS_NAME) - it.requestSecret(verifyingDeviceId, KEYBACKUP_SECRET_SSSS_NAME) - } - } else { - Timber.tag(TAG).i("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") - } + val verificationResponse = receive() + val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") + val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) + if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifying_device_key) { + Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") + return; } - return newSession + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + + // TODO: what do we do with the master key? +// verificationResponse.master_key ?.let { +// // set master key as trusted +// crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) +// } + + // request secrets from the verifying device + Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + + session.sharedSecretStorageService() .let { + it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) + } } private suspend fun receive(): Payload? { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index f97a59b432..c729152e44 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -16,24 +16,31 @@ package im.vector.app.features.login.qr +import android.content.Context import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory 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.features.home.HomeActivity import kotlinx.coroutines.Dispatchers 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.internal.rendezvous.Rendezvous import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason import timber.log.Timber 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(initialState) { - val TAG: String = QrCodeLoginViewModel::class.java.simpleName @AssistedFactory @@ -81,44 +88,31 @@ class QrCodeLoginViewModel @AssistedInject constructor( _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) viewModelScope.launch(Dispatchers.IO) { - val confirmationCode = rendezvous.startAfterScanningCode() - Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") - confirmationCode ?.let { - onConnectionEstablished(it) - rendezvous.completeOnNewDevice() + try { + val confirmationCode = rendezvous.startAfterScanningCode() + Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") + confirmationCode?.let { + onConnectionEstablished(it) + val session = rendezvous.waitForLoginOnNewDevice(authenticationService) + onSigningIn() + session?.let { + activeSessionHolder.setActiveSession(session) + authenticationService.reset() + + session.configureAndStart(applicationContext) + + rendezvous.completeVerificationOnNewDevice(session) + + _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen) + } + } + } catch (failure: Throwable) { + Timber.tag(TAG).e(failure, "Error occurred during sign in") + onFailed(RendezvousFailureReason.Unknown) } } - // if (isValidQrCode(action.qrCode)) { -// setState { -// copy( -// connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice -// ) -// } -// _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) -// } -// - -// // TODO. UI test purpose. Fixme remove! -// 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() -// } } - private fun onFailed(reason: RendezvousFailureReason) { setState { copy( @@ -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. */