WIP / Verify from passphrase UX
This commit is contained in:
parent
cb669ad881
commit
9a08f5ec4e
@ -42,6 +42,11 @@ interface CrossSigningService {
|
|||||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||||
callback: MatrixCallback<Unit>? = null)
|
callback: MatrixCallback<Unit>? = null)
|
||||||
|
|
||||||
|
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||||
|
uskKeyPrivateKey: String?,
|
||||||
|
sskPrivateKey: String?,
|
||||||
|
callback: MatrixCallback<Unit>? = null) : UserTrustResult
|
||||||
|
|
||||||
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
|
||||||
|
|
||||||
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.crypto.crosssigning
|
||||||
|
|
||||||
|
const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master"
|
||||||
|
|
||||||
|
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
|
||||||
|
|
||||||
|
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.securestorage
|
||||||
|
|
||||||
|
sealed class IntegrityResult {
|
||||||
|
data class Success(val passphraseBased: Boolean) : IntegrityResult()
|
||||||
|
data class Error(val cause: SharedSecretStorageError) : IntegrityResult()
|
||||||
|
}
|
@ -107,6 +107,7 @@ interface SharedSecretStorageService {
|
|||||||
* @param secretKey the secret key to use (@see #Curve25519AesSha2KeySpec)
|
* @param secretKey the secret key to use (@see #Curve25519AesSha2KeySpec)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Throws
|
fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback<String>)
|
||||||
fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>)
|
|
||||||
|
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) : IntegrityResult
|
||||||
}
|
}
|
||||||
|
@ -292,6 +292,76 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
cryptoStore.clearOtherUserTrust()
|
cryptoStore.clearOtherUserTrust()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, sskPrivateKey: String?, callback: MatrixCallback<Unit>?): UserTrustResult {
|
||||||
|
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId)
|
||||||
|
|
||||||
|
var masterKeyIsTrusted = false
|
||||||
|
var userKeyIsTrusted = false
|
||||||
|
var selfSignedKeyIsTrusted = false
|
||||||
|
|
||||||
|
masterKeyPrivateKey?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
try {
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
|
masterPkSigning?.releaseSigning()
|
||||||
|
masterPkSigning = pkSigning
|
||||||
|
masterKeyIsTrusted = true
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uskKeyPrivateKey?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
try {
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
|
userPkSigning?.releaseSigning()
|
||||||
|
userPkSigning = pkSigning
|
||||||
|
userKeyIsTrusted = true
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sskPrivateKey?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
|
val pkSigning = OlmPkSigning()
|
||||||
|
try {
|
||||||
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
|
selfSigningPkSigning?.releaseSigning()
|
||||||
|
selfSigningPkSigning = pkSigning
|
||||||
|
selfSignedKeyIsTrusted = true
|
||||||
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
|
} else {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
pkSigning.releaseSigning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
|
||||||
|
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
|
||||||
|
} else {
|
||||||
|
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||||
|
val checkSelfTrust = checkSelfTrust()
|
||||||
|
if (checkSelfTrust.isVerified()) {
|
||||||
|
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey)
|
||||||
|
}
|
||||||
|
return checkSelfTrust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
|
||||||
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
|||||||
import im.vector.matrix.android.api.session.events.model.toContent
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
||||||
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeySigner
|
import im.vector.matrix.android.api.session.securestorage.KeySigner
|
||||||
@ -327,4 +328,37 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||||||
const val ENCRYPTED = "encrypted"
|
const val ENCRYPTED = "encrypted"
|
||||||
const val DEFAULT_KEY_ID = "m.secret_storage.default_key"
|
const val DEFAULT_KEY_ID = "m.secret_storage.default_key"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?): IntegrityResult {
|
||||||
|
|
||||||
|
if (secretNames.isEmpty()) {
|
||||||
|
return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret("none"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyInfoResult = if (keyId == null) {
|
||||||
|
getDefaultKey()
|
||||||
|
} else {
|
||||||
|
getKey(keyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
|
||||||
|
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
|
||||||
|
|
||||||
|
if (keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
|
||||||
|
// Unsupported algorithm
|
||||||
|
return IntegrityResult.Error(
|
||||||
|
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretNames.forEach { secretName ->
|
||||||
|
val secretEvent = accountDataService.getAccountDataEvent(secretName)
|
||||||
|
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret(secretName))
|
||||||
|
if ((secretEvent.content["encrypted"] as? Map<*, *>)?.get(keyInfo.id) == null) {
|
||||||
|
return IntegrityResult.Error(SharedSecretStorageError.SecretNotEncryptedWithKey(secretName, keyInfo.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntegrityResult.Success(keyInfo.content.passphrase != null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||||||
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
|
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
|
||||||
timelineEventMapper.map(it, buildReadReceipts = false)
|
timelineEventMapper.map(it, buildReadReceipts = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return RoomSummary(
|
return RoomSummary(
|
||||||
roomId = roomSummaryEntity.roomId,
|
roomId = roomSummaryEntity.roomId,
|
||||||
displayName = roomSummaryEntity.displayName ?: "",
|
displayName = roomSummaryEntity.displayName ?: "",
|
||||||
|
@ -30,7 +30,8 @@ sealed class SharedSecureStorageAction : VectorViewModelAction {
|
|||||||
sealed class SharedSecureStorageViewEvent : VectorViewEvents {
|
sealed class SharedSecureStorageViewEvent : VectorViewEvents {
|
||||||
|
|
||||||
object Dismiss : SharedSecureStorageViewEvent()
|
object Dismiss : SharedSecureStorageViewEvent()
|
||||||
data class Error(val message: String) : SharedSecureStorageViewEvent()
|
data class FinishSuccess(val cypherResult: String) : SharedSecureStorageViewEvent()
|
||||||
|
data class Error(val message: String, val dismiss: Boolean = false) : SharedSecureStorageViewEvent()
|
||||||
data class InlineError(val message: String) : SharedSecureStorageViewEvent()
|
data class InlineError(val message: String) : SharedSecureStorageViewEvent()
|
||||||
object ShowModalLoading : SharedSecureStorageViewEvent()
|
object ShowModalLoading : SharedSecureStorageViewEvent()
|
||||||
object HideModalLoading : SharedSecureStorageViewEvent()
|
object HideModalLoading : SharedSecureStorageViewEvent()
|
||||||
|
@ -22,6 +22,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.airbnb.mvrx.MvRx
|
import com.airbnb.mvrx.MvRx
|
||||||
import com.airbnb.mvrx.viewModel
|
import com.airbnb.mvrx.viewModel
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
@ -34,6 +35,7 @@ import io.reactivex.disposables.CompositeDisposable
|
|||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.activity.*
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
||||||
@ -78,7 +80,7 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
|||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
viewModel.subscribe(this) {
|
viewModel.subscribe(this) {
|
||||||
// renderState(it)
|
// renderState(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +90,19 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
|||||||
setResult(Activity.RESULT_CANCELED)
|
setResult(Activity.RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
is SharedSecureStorageViewEvent.Error -> {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(getString(R.string.dialog_title_error))
|
||||||
|
.setMessage(it.message)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
if (it.dismiss) {
|
||||||
|
setResult(Activity.RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
is SharedSecureStorageViewEvent.ShowModalLoading -> {
|
is SharedSecureStorageViewEvent.ShowModalLoading -> {
|
||||||
showWaitingView()
|
showWaitingView()
|
||||||
}
|
}
|
||||||
@ -97,6 +112,12 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
|||||||
is SharedSecureStorageViewEvent.UpdateLoadingState -> {
|
is SharedSecureStorageViewEvent.UpdateLoadingState -> {
|
||||||
updateWaitingView(it.waitingData)
|
updateWaitingView(it.waitingData)
|
||||||
}
|
}
|
||||||
|
is SharedSecureStorageViewEvent.FinishSuccess -> {
|
||||||
|
val dataResult = Intent()
|
||||||
|
dataResult.putExtra(EXTRA_DATA_RESULT, it.cypherResult)
|
||||||
|
setResult(Activity.RESULT_OK, dataResult)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +126,7 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT"
|
||||||
const val RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"
|
const val RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"
|
||||||
fun newIntent(context: Context, keyId: String? = null, requestedSecrets: List<String>, resultKeyStoreAlias: String = RESULT_KEYSTORE_ALIAS): Intent {
|
fun newIntent(context: Context, keyId: String? = null, requestedSecrets: List<String>, resultKeyStoreAlias: String = RESULT_KEYSTORE_ALIAS): Intent {
|
||||||
require(requestedSecrets.isNotEmpty())
|
require(requestedSecrets.isNotEmpty())
|
||||||
|
@ -22,33 +22,53 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
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.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
||||||
|
import im.vector.matrix.android.api.session.securestorage.IntegrityResult
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
|
import im.vector.matrix.android.internal.util.awaitCallback
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
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.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
data class SharedSecureStorageViewState(
|
data class SharedSecureStorageViewState(
|
||||||
val requestedSecrets: List<String> = emptyList(),
|
|
||||||
val passphraseVisible: Boolean = false
|
val passphraseVisible: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
class SharedSecureStorageViewModel @AssistedInject constructor(
|
class SharedSecureStorageViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: SharedSecureStorageViewState,
|
@Assisted initialState: SharedSecureStorageViewState,
|
||||||
|
@Assisted val args: SharedSecureStorageActivity.Args,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
: VectorViewModel<SharedSecureStorageViewState, SharedSecureStorageAction, SharedSecureStorageViewEvent>(initialState) {
|
: VectorViewModel<SharedSecureStorageViewState, SharedSecureStorageAction, SharedSecureStorageViewEvent>(initialState) {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(initialState: SharedSecureStorageViewState): SharedSecureStorageViewModel
|
fun create(initialState: SharedSecureStorageViewState, args: SharedSecureStorageActivity.Args): SharedSecureStorageViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: SharedSecureStorageAction) = withState { state ->
|
init {
|
||||||
|
val isValid = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(args.requestedSecrets, args.keyId) is IntegrityResult.Success
|
||||||
|
if (!isValid) {
|
||||||
|
_viewEvents.post(
|
||||||
|
SharedSecureStorageViewEvent.Error(
|
||||||
|
stringProvider.getString(R.string.enter_secret_storage_invalid),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: SharedSecureStorageAction) = withState {
|
||||||
when (action) {
|
when (action) {
|
||||||
is SharedSecureStorageAction.TogglePasswordVisibility -> {
|
is SharedSecureStorageAction.TogglePasswordVisibility -> {
|
||||||
setState {
|
setState {
|
||||||
@ -61,58 +81,68 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
|
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
|
||||||
}
|
}
|
||||||
is SharedSecureStorageAction.SubmitPassphrase -> {
|
is SharedSecureStorageAction.SubmitPassphrase -> {
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
|
val decryptedSecretMap = HashMap<String, String>()
|
||||||
val passphrase = action.passphrase
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
|
runCatching {
|
||||||
if (!keyInfoResult.isSuccess()) {
|
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
val passphrase = action.passphrase
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key"))
|
val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
|
||||||
return@withState
|
if (!keyInfoResult.isSuccess()) {
|
||||||
}
|
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||||
val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo
|
_viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key"))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo
|
||||||
|
|
||||||
// TODO
|
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
|
||||||
// val decryptedSecretMap = HashMap<String, String>()
|
WaitingViewData(
|
||||||
// val errors = ArrayList<Throwable>()
|
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
|
isIndeterminate = true
|
||||||
WaitingViewData(
|
)
|
||||||
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
))
|
||||||
isIndeterminate = true
|
val keySpec = Curve25519AesSha2KeySpec.fromPassphrase(
|
||||||
|
passphrase,
|
||||||
|
keyInfo.content.passphrase?.salt ?: "",
|
||||||
|
keyInfo.content.passphrase?.iterations ?: 0,
|
||||||
|
// TODO
|
||||||
|
object : ProgressListener {
|
||||||
|
override fun onProgress(progress: Int, total: Int) {
|
||||||
|
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
|
||||||
|
WaitingViewData(
|
||||||
|
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
||||||
|
isIndeterminate = false,
|
||||||
|
progress = progress,
|
||||||
|
progressTotal = total
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
))
|
|
||||||
state.requestedSecrets.forEach {
|
|
||||||
val keySpec = Curve25519AesSha2KeySpec.fromPassphrase(
|
|
||||||
passphrase,
|
|
||||||
keyInfo.content.passphrase?.salt ?: "",
|
|
||||||
keyInfo.content.passphrase?.iterations ?: 0,
|
|
||||||
// TODO
|
|
||||||
object : ProgressListener {
|
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState(
|
|
||||||
WaitingViewData(
|
|
||||||
message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message),
|
|
||||||
isIndeterminate = false,
|
|
||||||
progress = progress,
|
|
||||||
progressTotal = total
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
session.sharedSecretStorageService.getSecret(
|
|
||||||
name = it,
|
|
||||||
keyId = keyInfo.id,
|
|
||||||
secretKey = keySpec,
|
|
||||||
callback = object : MatrixCallback<String> {
|
|
||||||
override fun onSuccess(data: String) {
|
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
withContext(Dispatchers.IO) {
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
args.requestedSecrets.forEach {
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.InlineError(failure.localizedMessage))
|
val res = awaitCallback<String> { callback ->
|
||||||
|
session.sharedSecretStorageService.getSecret(
|
||||||
|
name = it,
|
||||||
|
keyId = keyInfo.id,
|
||||||
|
secretKey = keySpec,
|
||||||
|
callback = callback)
|
||||||
}
|
}
|
||||||
})
|
decryptedSecretMap[it] = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.fold({
|
||||||
|
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||||
|
val safeForIntentCypher = ByteArrayOutputStream().also {
|
||||||
|
it.use {
|
||||||
|
session.securelyStoreObject(decryptedSecretMap as Map<String,String>, args.resultKeyStoreAlias, it)
|
||||||
|
}
|
||||||
|
}.toByteArray().toBase64NoPadding()
|
||||||
|
_viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher))
|
||||||
|
}, {
|
||||||
|
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||||
|
_viewEvents.post(SharedSecureStorageViewEvent.InlineError(it.localizedMessage))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,11 +154,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? {
|
||||||
val activity: SharedSecureStorageActivity = viewModelContext.activity()
|
val activity: SharedSecureStorageActivity = viewModelContext.activity()
|
||||||
val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(MvRx.KEY_ARG)
|
val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(MvRx.KEY_ARG)
|
||||||
return activity.viewModelFactory.create(
|
return activity.viewModelFactory.create(state, args)
|
||||||
SharedSecureStorageViewState(
|
|
||||||
requestedSecrets = args.requestedSecrets
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,15 @@ import butterknife.OnClick
|
|||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.jakewharton.rxbinding3.view.clicks
|
||||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.showPassword
|
import im.vector.riotx.core.extensions.showPassword
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
|
class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
|
||||||
@ -83,7 +85,8 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
|
|||||||
reasonText.text = getString(R.string.enter_secret_storage_passphrase_reason_verify)
|
reasonText.text = getString(R.string.enter_secret_storage_passphrase_reason_verify)
|
||||||
|
|
||||||
mPassphraseTextEdit.editorActionEvents()
|
mPassphraseTextEdit.editorActionEvents()
|
||||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
submit()
|
submit()
|
||||||
@ -91,13 +94,6 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
|
|||||||
}
|
}
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
mPassphraseTextEdit.setOnEditorActionListener { _, actionId, _ ->
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
|
||||||
submit()
|
|
||||||
return@setOnEditorActionListener true
|
|
||||||
}
|
|
||||||
return@setOnEditorActionListener false
|
|
||||||
}
|
|
||||||
|
|
||||||
mPassphraseTextEdit.textChanges()
|
mPassphraseTextEdit.textChanges()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
@ -114,17 +110,21 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submitButton.setOnClickListener(DebouncedClickListener(
|
submitButton.clicks()
|
||||||
View.OnClickListener {
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe {
|
||||||
submit()
|
submit()
|
||||||
}
|
}
|
||||||
))
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
cancelButton.setOnClickListener(DebouncedClickListener(
|
cancelButton.clicks()
|
||||||
View.OnClickListener {
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe {
|
||||||
sharedViewModel.handle(SharedSecureStorageAction.Cancel)
|
sharedViewModel.handle(SharedSecureStorageAction.Cancel)
|
||||||
}
|
}
|
||||||
))
|
.disposeOnDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submit() {
|
fun submit() {
|
||||||
|
@ -30,4 +30,5 @@ sealed class VerificationAction : VectorViewModelAction {
|
|||||||
object GotItConclusion : VerificationAction()
|
object GotItConclusion : VerificationAction()
|
||||||
object SkipVerification : VerificationAction()
|
object SkipVerification : VerificationAction()
|
||||||
object VerifyFromPassphrase : VerificationAction()
|
object VerifyFromPassphrase : VerificationAction()
|
||||||
|
data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction()
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.crypto.verification
|
package im.vector.riotx.features.crypto.verification
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@ -30,6 +33,9 @@ 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.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
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
@ -87,15 +93,47 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
|
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
||||||
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
||||||
startActivity(SharedSecureStorageActivity.newIntent(requireContext(),null, listOf("m.cross_signing.user_signing")))
|
startActivityForResult(SharedSecureStorageActivity.newIntent(
|
||||||
|
requireContext(),
|
||||||
|
null,// use default key
|
||||||
|
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME),
|
||||||
|
SharedSecureStorageActivity.RESULT_KEYSTORE_ALIAS
|
||||||
|
), SECRET_REQUEST_CODE)
|
||||||
|
}
|
||||||
|
is VerificationBottomSheetViewEvents.ModalError -> {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(getString(R.string.dialog_title_error))
|
||||||
|
.setMessage(it.errorMessage)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) {
|
||||||
|
data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)?.let {
|
||||||
|
viewModel.handle(VerificationAction.GotResultFromSsss(it, SharedSecureStorageActivity.RESULT_KEYSTORE_ALIAS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
|
||||||
|
if (state.selfVerificationMode && state.verifiedFromPrivateKeys) {
|
||||||
|
showFragment(VerificationConclusionFragment::class, Bundle().apply {
|
||||||
|
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))
|
||||||
|
})
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
state.otherUserMxItem?.let { matrixItem ->
|
state.otherUserMxItem?.let { matrixItem ->
|
||||||
if (state.isMe) {
|
if (state.isMe) {
|
||||||
if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) {
|
if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) {
|
||||||
@ -235,6 +273,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
const val SECRET_REQUEST_CODE = 101
|
||||||
|
|
||||||
fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet {
|
fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet {
|
||||||
return VerificationBottomSheet().apply {
|
return VerificationBottomSheet().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
@ -248,7 +289,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forSelfVerification(session: Session) : VerificationBottomSheet{
|
fun forSelfVerification(session: Session): VerificationBottomSheet {
|
||||||
return VerificationBottomSheet().apply {
|
return VerificationBottomSheet().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putParcelable(MvRx.KEY_ARG, VerificationArgs(
|
putParcelable(MvRx.KEY_ARG, VerificationArgs(
|
||||||
|
@ -24,4 +24,5 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
|||||||
sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
|
sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
|
||||||
object Dismiss : VerificationBottomSheetViewEvents()
|
object Dismiss : VerificationBottomSheetViewEvents()
|
||||||
object AccessSecretStore : VerificationBottomSheetViewEvents()
|
object AccessSecretStore : VerificationBottomSheetViewEvents()
|
||||||
|
data class ModalError(val errorMessage: CharSequence) : VerificationBottomSheetViewEvents()
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,9 @@ 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.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
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
|
||||||
@ -39,6 +42,8 @@ import im.vector.matrix.android.api.session.events.model.LocalEcho
|
|||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
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.fromBase64NoPadding
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
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
|
||||||
@ -53,6 +58,7 @@ data class VerificationBottomSheetViewState(
|
|||||||
val transactionId: String? = null,
|
val transactionId: String? = null,
|
||||||
// true when we display the loading and we wait for the other (incoming request)
|
// true when we display the loading and we wait for the other (incoming request)
|
||||||
val selfVerificationMode: Boolean = false,
|
val selfVerificationMode: Boolean = false,
|
||||||
|
val verifiedFromPrivateKeys: Boolean = false,
|
||||||
val isMe: Boolean = false
|
val isMe: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
@ -250,12 +256,36 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
|||||||
is VerificationAction.GotItConclusion -> {
|
is VerificationAction.GotItConclusion -> {
|
||||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||||
}
|
}
|
||||||
is VerificationAction.SkipVerification -> {
|
is VerificationAction.SkipVerification -> {
|
||||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||||
}
|
}
|
||||||
is VerificationAction.VerifyFromPassphrase -> {
|
is VerificationAction.VerifyFromPassphrase -> {
|
||||||
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
|
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
|
||||||
}
|
}
|
||||||
|
is VerificationAction.GotResultFromSsss -> {
|
||||||
|
try {
|
||||||
|
action.cypherData.fromBase64NoPadding().inputStream().use { ins ->
|
||||||
|
val res = session.loadSecureSecret<Map<String, String>>(ins, action.alias)
|
||||||
|
val trustResult = session.getCrossSigningService().checkTrustFromPrivateKeys(
|
||||||
|
res?.get(MASTER_KEY_SSSS_NAME),
|
||||||
|
res?.get(USER_SIGNING_KEY_SSSS_NAME),
|
||||||
|
res?.get(SELF_SIGNING_KEY_SSSS_NAME)
|
||||||
|
)
|
||||||
|
if (trustResult.isVerified()) {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ class CrossSigningEpoxyController @Inject constructor(
|
|||||||
interface InteractionListener {
|
interface InteractionListener {
|
||||||
fun onInitializeCrossSigningKeys()
|
fun onInitializeCrossSigningKeys()
|
||||||
fun onResetCrossSigningKeys()
|
fun onResetCrossSigningKeys()
|
||||||
|
fun verifySession()
|
||||||
}
|
}
|
||||||
|
|
||||||
var interactionListener: InteractionListener? = null
|
var interactionListener: InteractionListener? = null
|
||||||
@ -77,21 +78,31 @@ class CrossSigningEpoxyController @Inject constructor(
|
|||||||
interactionListener?.onResetCrossSigningKeys()
|
interactionListener?.onResetCrossSigningKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (data.xSigningIsEnableInAccount) {
|
}
|
||||||
genericItem {
|
} else if (data.xSigningIsEnableInAccount) {
|
||||||
id("enable")
|
genericItem {
|
||||||
titleIconResourceId(R.drawable.ic_shield_black)
|
id("enable")
|
||||||
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted))
|
titleIconResourceId(R.drawable.ic_shield_black)
|
||||||
|
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted))
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("verify")
|
||||||
|
title(stringProvider.getString(R.string.complete_security))
|
||||||
|
titleColor(colorProvider.getColor(R.color.riotx_positive_accent))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(colorProvider.getColor(R.color.riotx_positive_accent))
|
||||||
|
listener {
|
||||||
|
interactionListener?.verifySession()
|
||||||
}
|
}
|
||||||
bottomSheetVerificationActionItem {
|
}
|
||||||
id("resetkeys")
|
bottomSheetVerificationActionItem {
|
||||||
title("Reset keys")
|
id("resetkeys")
|
||||||
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
title("Reset keys")
|
||||||
iconRes(R.drawable.ic_arrow_right)
|
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
listener {
|
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
interactionListener?.onResetCrossSigningKeys()
|
listener {
|
||||||
}
|
interactionListener?.onResetCrossSigningKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -54,6 +54,11 @@ class CrossSigningSettingsFragment @Inject constructor(
|
|||||||
is CrossSigningSettingsViewEvents.RequestPassword -> {
|
is CrossSigningSettingsViewEvents.RequestPassword -> {
|
||||||
requestPassword()
|
requestPassword()
|
||||||
}
|
}
|
||||||
|
CrossSigningSettingsViewEvents.VerifySession -> {
|
||||||
|
(requireActivity() as? VectorBaseActivity)?.let { activity ->
|
||||||
|
activity.navigator.waitSessionVerification(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +98,10 @@ class CrossSigningSettingsFragment @Inject constructor(
|
|||||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning)
|
viewModel.handle(CrossSigningAction.InitializeCrossSigning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun verifySession() {
|
||||||
|
viewModel.handle(CrossSigningAction.VerifySession)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResetCrossSigningKeys() {
|
override fun onResetCrossSigningKeys() {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.dialog_title_confirmation)
|
.setTitle(R.string.dialog_title_confirmation)
|
||||||
|
@ -25,4 +25,5 @@ sealed class CrossSigningSettingsViewEvents : VectorViewEvents {
|
|||||||
data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents()
|
data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents()
|
||||||
|
|
||||||
object RequestPassword : CrossSigningSettingsViewEvents()
|
object RequestPassword : CrossSigningSettingsViewEvents()
|
||||||
|
object VerifySession : CrossSigningSettingsViewEvents()
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ data class CrossSigningSettingsViewState(
|
|||||||
|
|
||||||
sealed class CrossSigningAction : VectorViewModelAction {
|
sealed class CrossSigningAction : VectorViewModelAction {
|
||||||
object InitializeCrossSigning : CrossSigningAction()
|
object InitializeCrossSigning : CrossSigningAction()
|
||||||
|
object VerifySession : CrossSigningAction()
|
||||||
data class PasswordEntered(val password: String) : CrossSigningAction()
|
data class PasswordEntered(val password: String) : CrossSigningAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +89,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat
|
|||||||
password = action.password
|
password = action.password
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
CrossSigningAction.VerifySession -> {
|
||||||
|
_viewEvents.post(CrossSigningSettingsViewEvents.VerifySession)
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
<string name="new_signin">New Sign In</string>
|
<string name="new_signin">New Sign In</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="enter_secret_storage_invalid">Cannot find secrets in storage</string>
|
||||||
<string name="enter_secret_storage_passphrase">Enter secret storage passphrase</string>
|
<string name="enter_secret_storage_passphrase">Enter secret storage passphrase</string>
|
||||||
<string name="enter_secret_storage_passphrase_warning">Warning:</string>
|
<string name="enter_secret_storage_passphrase_warning">Warning:</string>
|
||||||
<string name="enter_secret_storage_passphrase_warning_text">You should only access secret storage from a trusted device</string>
|
<string name="enter_secret_storage_passphrase_warning_text">You should only access secret storage from a trusted device</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user