Merge pull request #1215 from vector-im/feature/ssss_and_backup

Fixes #1214
This commit is contained in:
Valere 2020-04-10 12:10:42 +02:00 committed by GitHub
commit 3ba619d45c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 41 deletions

View File

@ -14,6 +14,7 @@ Improvements 🙌:
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
- Cross-Signing | Gossip key backup recovery key (#1200)
- Show room encryption status as a bubble tile (#1078)
- Cross-Signing | Restore history after recover from passphrase (#1214)
Bugfix 🐛:
- Missing avatar/displayname after verification request message (#841)

View File

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

View File

@ -32,6 +32,7 @@ import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
@ -108,7 +109,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
startActivityForResult(SharedSecureStorageActivity.newIntent(
requireContext(),
null, // use default key
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME),
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
), SECRET_REQUEST_CODE)
}

View File

@ -15,6 +15,7 @@
*/
package im.vector.riotx.features.crypto.verification
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
@ -28,6 +29,7 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
@ -46,8 +48,15 @@ import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.riotx.R
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import kotlinx.coroutines.launch
import timber.log.Timber
data class VerificationBottomSheetViewState(
@ -71,7 +80,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: VerificationBottomSheetViewState,
@Assisted val args: VerificationBottomSheet.VerificationArgs,
private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider)
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stringProvider: StringProvider)
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
VerificationService.Listener {
@ -334,40 +344,82 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
}
is 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)
}
} else {
// POP UP something
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError("Failed to import keys"))
}
}
} catch (failure: Throwable) {
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage))
}
handleSecretBackFromSSSS(action)
}
}.exhaustive
}
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
try {
action.cypherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<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) {
transactionUpdated(tx)
}

View File

@ -94,6 +94,8 @@
<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="error_failed_to_import_keys">Failed to import keys</string>
<!-- END Strings added by Valere -->