Suspend API: handle cross signing service

This commit is contained in:
ganfra 2022-03-30 17:35:33 +02:00
parent 0590258d54
commit 046699bc84
16 changed files with 275 additions and 329 deletions

View file

@ -125,21 +125,21 @@ class FlowSession(private val session: Session) {
} }
fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> { fun liveUserCryptoDevices(userId: String): Flow<List<CryptoDeviceInfo>> {
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() return session.cryptoService().getLiveCryptoDeviceInfo(userId)
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.cryptoService().getCryptoDeviceInfo(userId) session.cryptoService().getCryptoDeviceInfo(userId)
} }
} }
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> { fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId)
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional() session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
} }
} }
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> { fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys()
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional() session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
} }

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.crypto
import android.content.Context import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
@ -123,9 +124,9 @@ interface CryptoService {
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> fun getLiveCryptoDeviceInfo(userId: String): Flow<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> fun getLiveCryptoDeviceInfo(userIds: List<String>): Flow<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener) fun addNewSessionListener(newSessionListener: NewSessionListener)

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.crypto.crosssigning package org.matrix.android.sdk.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
@ -29,31 +30,30 @@ interface CrossSigningService {
/** /**
* Is our own device signed by our own cross signing identity * Is our own device signed by our own cross signing identity
*/ */
fun isCrossSigningVerified(): Boolean suspend fun isCrossSigningVerified(): Boolean
// TODO this isn't used anywhere besides in tests? // TODO this isn't used anywhere besides in tests?
// Is this the local trust concept that we have for devices? // Is this the local trust concept that we have for devices?
fun isUserTrusted(otherUserId: String): Boolean suspend fun isUserTrusted(otherUserId: String): Boolean
/** /**
* Will not force a download of the key, but will verify signatures trust chain. * Will not force a download of the key, but will verify signatures trust chain.
* Checks that my trusted user key has signed the other user UserKey * Checks that my trusted user key has signed the other user UserKey
*/ */
fun checkUserTrust(otherUserId: String): UserTrustResult suspend fun checkUserTrust(otherUserId: String): UserTrustResult
/** /**
* Initialize cross signing for this user. * Initialize cross signing for this user.
* Users needs to enter credentials * Users needs to enter credentials
*/ */
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
callback: MatrixCallback<Unit>)
/** /**
* Does our own user have a valid cross signing identity uploaded. * Does our own user have a valid cross signing identity uploaded.
* *
* In other words has any of our devices uploaded public cross signing keys to the server. * In other words has any of our devices uploaded public cross signing keys to the server.
*/ */
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null suspend fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
/** /**
* Inject the private cross signing keys, likely from backup, into our store. * Inject the private cross signing keys, likely from backup, into our store.
@ -70,17 +70,17 @@ interface CrossSigningService {
* *
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys. * @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
*/ */
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>>
/** Get our own public cross signing keys */ /** Get our own public cross signing keys */
fun getMyCrossSigningKeys(): MXCrossSigningInfo? suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
/** Get our own private cross signing keys */ /** Get our own private cross signing keys */
fun getCrossSigningPrivateKeys(): PrivateKeysInfo? suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>>
/** /**
* Can we sign our other devices or other users? * Can we sign our other devices or other users?
@ -93,11 +93,11 @@ interface CrossSigningService {
fun allPrivateKeysKnown(): Boolean fun allPrivateKeysKnown(): Boolean
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */ /** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
fun trustUser(otherUserId: String, suspend fun trustUser(otherUserId: String,
callback: MatrixCallback<Unit>) callback: MatrixCallback<Unit>)
/** Mark our own master key as trusted */ /** Mark our own master key as trusted */
fun markMyMasterKeyAsTrusted() suspend fun markMyMasterKeyAsTrusted()
/** /**
* Sign one of your devices and upload the signature * Sign one of your devices and upload the signature
@ -114,7 +114,7 @@ interface CrossSigningService {
* using the self-signing key for our own devices or using the user-signing key and the master * using the self-signing key for our own devices or using the user-signing key and the master
* key of another user. * key of another user.
*/ */
fun checkDeviceTrust(otherUserId: String, suspend fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String, otherDeviceId: String,
// TODO what is locallyTrusted used for? // TODO what is locallyTrusted used for?
locallyTrusted: Boolean?): DeviceTrustResult locallyTrusted: Boolean?): DeviceTrustResult

View file

@ -25,6 +25,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.joinAll import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -426,14 +427,12 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(userId: String): Flow<List<CryptoDeviceInfo>> {
return getLiveCryptoDeviceInfo(listOf(userId)) return getLiveCryptoDeviceInfo(listOf(userId))
} }
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
return runBlocking { return olmMachine.getLiveDevices(userIds)
this@DefaultCryptoService.olmMachine.getLiveDevices(userIds) // ?: LiveDevice(userIds, deviceObserver)
}
} }
/** /**

View file

@ -16,9 +16,11 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -64,7 +66,7 @@ import uniffi.olm.setLogger
import java.io.File import java.io.File
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList
import uniffi.olm.OlmMachine as InnerMachine import uniffi.olm.OlmMachine as InnerMachine
import uniffi.olm.ProgressListener as RustProgressListener import uniffi.olm.ProgressListener as RustProgressListener
import uniffi.olm.UserIdentity as RustUserIdentity import uniffi.olm.UserIdentity as RustUserIdentity
@ -81,98 +83,29 @@ private class CryptoProgressListener(private val listener: ProgressListener?) :
} }
} }
internal class LiveDevice( private data class UserIdentityCollector(val userId: String, val collector: SendChannel<Optional<MXCrossSigningInfo>>) : SendChannel<Optional<MXCrossSigningInfo>> by collector
internal var userIds: List<String>, private data class DevicesCollector(val userIds: List<String>, val collector: SendChannel<List<CryptoDeviceInfo>>) : SendChannel<List<CryptoDeviceInfo>> by collector
private var observer: DeviceUpdateObserver private typealias PrivateKeysCollector = SendChannel<Optional<PrivateKeysInfo>>
) : MutableLiveData<List<CryptoDeviceInfo>>() {
override fun onActive() { private class FlowCollectors {
observer.addDeviceUpdateListener(this) val userIdentityCollectors = CopyOnWriteArrayList<UserIdentityCollector>()
} val privateKeyCollectors = CopyOnWriteArrayList<PrivateKeysCollector>()
val deviceCollectors = CopyOnWriteArrayList<DevicesCollector>()
override fun onInactive() {
observer.removeDeviceUpdateListener(this)
}
}
internal class LiveUserIdentity(
internal var userId: String,
private var observer: UserIdentityUpdateObserver
) : MutableLiveData<Optional<MXCrossSigningInfo>>() {
override fun onActive() {
observer.addUserIdentityUpdateListener(this)
}
override fun onInactive() {
observer.removeUserIdentityUpdateListener(this)
}
}
internal class LivePrivateCrossSigningKeys(
private var observer: PrivateCrossSigningKeysUpdateObserver,
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
override fun onActive() {
observer.addUserIdentityUpdateListener(this)
}
override fun onInactive() {
observer.removeUserIdentityUpdateListener(this)
}
} }
fun setRustLogger() { fun setRustLogger() {
setLogger(CryptoLogger() as Logger) setLogger(CryptoLogger() as Logger)
} }
internal class DeviceUpdateObserver {
internal val listeners = ConcurrentHashMap<LiveDevice, List<String>>()
fun addDeviceUpdateListener(device: LiveDevice) {
listeners[device] = device.userIds
}
fun removeDeviceUpdateListener(device: LiveDevice) {
listeners.remove(device)
}
}
internal class UserIdentityUpdateObserver {
internal val listeners = ConcurrentHashMap<LiveUserIdentity, String>()
fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
listeners[userIdentity] = userIdentity.userId
}
fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
listeners.remove(userIdentity)
}
}
internal class PrivateCrossSigningKeysUpdateObserver {
internal val listeners = ConcurrentHashMap<LivePrivateCrossSigningKeys, Unit>()
fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
listeners[liveKeys] = Unit
}
fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
listeners.remove(liveKeys)
}
}
internal class OlmMachine( internal class OlmMachine(
user_id: String, user_id: String,
device_id: String, device_id: String,
path: File, path: File,
deviceObserver: DeviceUpdateObserver,
private val requestSender: RequestSender, private val requestSender: RequestSender,
) { ) {
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString()) private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
private val deviceUpdateObserver = deviceObserver
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
internal val verificationListeners = ArrayList<VerificationService.Listener>() internal val verificationListeners = ArrayList<VerificationService.Listener>()
private val flowCollectors = FlowCollectors()
/** Get our own user ID. */ /** Get our own user ID. */
fun userId(): String { fun userId(): String {
@ -193,26 +126,24 @@ internal class OlmMachine(
return this.inner return this.inner
} }
/** Update all of our live device listeners. */
private suspend fun updateLiveDevices() { private suspend fun updateLiveDevices() {
for ((liveDevice, users) in deviceUpdateObserver.listeners) { for (deviceCollector in flowCollectors.deviceCollectors) {
val devices = getCryptoDeviceInfo(users) val devices = getCryptoDeviceInfo(deviceCollector.userIds)
liveDevice.postValue(devices) deviceCollector.send(devices)
} }
} }
private suspend fun updateLiveUserIdentities() { private suspend fun updateLiveUserIdentities() {
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) { for (userIdentityCollector in flowCollectors.userIdentityCollectors) {
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional() val identity = getIdentity(userIdentityCollector.userId)?.toMxCrossSigningInfo()
liveIdentity.postValue(identity) userIdentityCollector.send(identity.toOptional())
} }
} }
private suspend fun updateLivePrivateKeys() { private suspend fun updateLivePrivateKeys() {
val keys = this.exportCrossSigningKeys().toOptional() val keys = this.exportCrossSigningKeys().toOptional()
for (privateKeyCollector in flowCollectors.privateKeyCollectors) {
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) { privateKeyCollector.send(keys)
liveKeys.postValue(keys)
} }
} }
@ -712,20 +643,27 @@ internal class OlmMachine(
return getUserDevicesMap(userIds) return getUserDevicesMap(userIds)
} }
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> { fun getLiveUserIdentity(userId: String): Flow<Optional<MXCrossSigningInfo>> {
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional() return channelFlow {
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver) val userIdentityCollector = UserIdentityCollector(userId, this)
liveIdentity.value = identity flowCollectors.userIdentityCollectors.add(userIdentityCollector)
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
return liveIdentity send(identity)
awaitClose {
flowCollectors.userIdentityCollectors.remove(userIdentityCollector)
}
}
} }
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> { fun getLivePrivateCrossSigningKeys(): Flow<Optional<PrivateKeysInfo>> {
val keys = this.exportCrossSigningKeys().toOptional() return channelFlow {
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver) flowCollectors.privateKeyCollectors.add(this)
liveKeys.value = keys val keys = this@OlmMachine.exportCrossSigningKeys().toOptional()
send(keys)
return liveKeys awaitClose {
flowCollectors.privateKeyCollectors.remove(this)
}
}
} }
/** /**
@ -738,12 +676,16 @@ internal class OlmMachine(
* *
* @return The list of Devices or an empty list if there aren't any. * @return The list of Devices or an empty list if there aren't any.
*/ */
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> { fun getLiveDevices(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
val plainDevices = getCryptoDeviceInfo(userIds) return channelFlow {
val devices = LiveDevice(userIds, deviceUpdateObserver) val devicesCollector = DevicesCollector(userIds, this)
devices.value = plainDevices flowCollectors.deviceCollectors.add(devicesCollector)
val devices = getCryptoDeviceInfo(userIds)
return devices send(devices)
awaitClose {
flowCollectors.deviceCollectors.remove(devicesCollector)
}
}
} }
/** Discard the currently active room key for the given room if there is one. */ /** Discard the currently active room key for the given room if there is one. */

View file

@ -31,7 +31,5 @@ internal class OlmMachineProvider @Inject constructor(
requestSender: RequestSender requestSender: RequestSender
) { ) {
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver() var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, requestSender)
var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, requestSender)
} }

View file

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -45,14 +45,14 @@ internal class RustCrossSigningService @Inject constructor(
/** /**
* Is our own device signed by our own cross signing identity * Is our own device signed by our own cross signing identity
*/ */
override fun isCrossSigningVerified(): Boolean { override suspend fun isCrossSigningVerified(): Boolean {
return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) { return when (val identity = olmMachine.getIdentity(olmMachine.userId()) ) {
is OwnUserIdentity -> identity.trustsOurOwnDevice() is OwnUserIdentity -> identity.trustsOurOwnDevice()
else -> false else -> false
} }
} }
override fun isUserTrusted(otherUserId: String): Boolean { override suspend fun isUserTrusted(otherUserId: String): Boolean {
// This seems to be used only in tests. // This seems to be used only in tests.
return this.checkUserTrust(otherUserId).isVerified() return this.checkUserTrust(otherUserId).isVerified()
} }
@ -61,14 +61,14 @@ internal class RustCrossSigningService @Inject constructor(
* Will not force a download of the key, but will verify signatures trust chain. * Will not force a download of the key, but will verify signatures trust chain.
* Checks that my trusted user key has signed the other user UserKey * Checks that my trusted user key has signed the other user UserKey
*/ */
override fun checkUserTrust(otherUserId: String): UserTrustResult { override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) } val identity = olmMachine.getIdentity(olmMachine.userId())
// While UserTrustResult has many different states, they are by the callers // While UserTrustResult has many different states, they are by the callers
// converted to a boolean value immediately, thus we don't need to support // converted to a boolean value immediately, thus we don't need to support
// all the values. // all the values.
return if (identity != null) { return if (identity != null) {
val verified = runBlocking { identity.verified() } val verified = identity.verified()
if (verified) { if (verified) {
UserTrustResult.Success UserTrustResult.Success
@ -84,8 +84,8 @@ internal class RustCrossSigningService @Inject constructor(
* Initialize cross signing for this user. * Initialize cross signing for this user.
* Users needs to enter credentials * Users needs to enter credentials
*/ */
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) { override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) } olmMachine.bootstrapCrossSigning(uiaInterceptor)
} }
/** /**
@ -108,26 +108,26 @@ internal class RustCrossSigningService @Inject constructor(
* *
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys. * @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
*/ */
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() } return olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo()
} }
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> { override fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return runBlocking { olmMachine.getLiveUserIdentity(userId) } return olmMachine.getLiveUserIdentity(userId)
} }
/** Get our own public cross signing keys */ /** Get our own public cross signing keys */
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return getUserCrossSigningKeys(olmMachine.userId()) return getUserCrossSigningKeys(olmMachine.userId())
} }
/** Get our own private cross signing keys */ /** Get our own private cross signing keys */
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return runBlocking { olmMachine.exportCrossSigningKeys() } return olmMachine.exportCrossSigningKeys()
} }
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> { override fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() } return olmMachine.getLivePrivateCrossSigningKeys()
} }
/** /**
@ -148,12 +148,12 @@ internal class RustCrossSigningService @Inject constructor(
} }
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */ /** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) { override suspend fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
// This is only used in a test // This is only used in a test
val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) } val userIdentity = olmMachine.getIdentity(otherUserId)
if (userIdentity != null) { if (userIdentity != null) {
runBlocking { userIdentity.verify() } userIdentity.verify()
callback.onSuccess(Unit) callback.onSuccess(Unit)
} else { } else {
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
@ -161,7 +161,7 @@ internal class RustCrossSigningService @Inject constructor(
} }
/** Mark our own master key as trusted */ /** Mark our own master key as trusted */
override fun markMyMasterKeyAsTrusted() { override suspend fun markMyMasterKeyAsTrusted() {
// This doesn't seem to be used? // This doesn't seem to be used?
this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback()) this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback())
} }
@ -171,10 +171,8 @@ internal class RustCrossSigningService @Inject constructor(
*/ */
override suspend fun trustDevice(deviceId: String) { override suspend fun trustDevice(deviceId: String) {
val device = olmMachine.getDevice(olmMachine.userId(), deviceId) val device = olmMachine.getDevice(olmMachine.userId(), deviceId)
if (device != null) {
return if (device != null) {
val verified = device.verify() val verified = device.verify()
if (verified) { if (verified) {
return return
} else { } else {
@ -192,15 +190,15 @@ internal class RustCrossSigningService @Inject constructor(
* using the self-signing key for our own devices or using the user-signing key and the master * using the self-signing key for our own devices or using the user-signing key and the master
* key of another user. * key of another user.
*/ */
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) } val device = olmMachine.getDevice(otherUserId, otherDeviceId)
return if (device != null) { return if (device != null) {
// TODO i don't quite understand the semantics here and there are no docs for // TODO i don't quite understand the semantics here and there are no docs for
// DeviceTrustResult, what do the different result types mean exactly, // DeviceTrustResult, what do the different result types mean exactly,
// do you return success only if the Device is cross signing verified? // do you return success only if the Device is cross signing verified?
// what about the local trust if it isn't? why is the local trust passed as an argument here? // what about the local trust if it isn't? why is the local trust passed as an argument here?
DeviceTrustResult.Success(runBlocking { device.trustLevel() }) DeviceTrustResult.Success(device.trustLevel())
} else { } else {
DeviceTrustResult.UnknownDevice(otherDeviceId) DeviceTrustResult.UnknownDevice(otherDeviceId)
} }

View file

@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -91,12 +90,7 @@ class BootstrapCrossSigningTask @Inject constructor(
) )
try { try {
awaitCallback<Unit> { crossSigningService.initializeCrossSigning(params.userInteractiveAuthInterceptor)
crossSigningService.initializeCrossSigning(
params.userInteractiveAuthInterceptor,
it
)
}
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) { if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
return BootstrapResult.SuccessCrossSigningOnly return BootstrapResult.SuccessCrossSigningOnly
} }

View file

@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -220,7 +219,6 @@ class HomeActivityViewModel @AssistedInject constructor(
// Try to initialize cross signing in background if possible // Try to initialize cross signing in background if possible
Timber.d("Initialize cross signing...") Timber.d("Initialize cross signing...")
try { try {
awaitCallback<Unit> {
session.cryptoService().crossSigningService().initializeCrossSigning( session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
@ -240,10 +238,8 @@ class HomeActivityViewModel @AssistedInject constructor(
} }
} }
}, },
callback = it
) )
Timber.d("Initialize cross signing SUCCESS") Timber.d("Initialize cross signing SUCCESS")
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "Failed to initialize cross signing") Timber.e(failure, "Failed to initialize cross signing")
} }

View file

@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.crypto.VerificationState
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -139,14 +140,19 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
} }
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration { private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration {
return if ( if (event.root.sendState != SendState.SYNCED)
event.root.sendState == SendState.SYNCED && return E2EDecoration.NONE
roomSummary?.isEncrypted.orFalse() && if (!roomSummary?.isEncrypted.orFalse())
// is user verified return E2EDecoration.NONE
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true) { val isUserVerified = runBlocking {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
}
if (!isUserVerified) {
return E2EDecoration.NONE
}
val ts = roomSummary?.encryptionEventTs ?: 0 val ts = roomSummary?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0 val eventTs = event.root.originServerTs ?: 0
if (event.isEncrypted()) { return if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info) // Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) { if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
E2EDecoration.NONE E2EDecoration.NONE
@ -183,9 +189,6 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
} }
} }
} else {
E2EDecoration.NONE
}
} }
/** /**

View file

@ -411,6 +411,7 @@ class DefaultNavigator @Inject constructor(
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) { override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {
// if cross signing is enabled and trusted or not set up at all we should propose full 4S // if cross signing is enabled and trusted or not set up at all we should propose full 4S
sessionHolder.getSafeActiveSession()?.let { session -> sessionHolder.getSafeActiveSession()?.let { session ->
coroutineScope.launch {
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null || if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
session.cryptoService().crossSigningService().canCrossSign()) { session.cryptoService().crossSigningService().canCrossSign()) {
(context as? AppCompatActivity)?.let { (context as? AppCompatActivity)?.let {
@ -421,6 +422,7 @@ class DefaultNavigator @Inject constructor(
} }
} }
} }
}
override fun open4SSetup(context: Context, setupMode: SetupMode) { override fun open4SSetup(context: Context, setupMode: SetupMode) {
if (context is AppCompatActivity) { if (context is AppCompatActivity) {

View file

@ -95,7 +95,6 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
room.flow().liveRoomMembers(roomMemberQueryParams) room.flow().liveRoomMembers(roomMemberQueryParams)
.flatMapLatest { membersSummary -> .flatMapLatest { membersSummary ->
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
.asFlow()
.catch { Timber.e(it) } .catch { Timber.e(it) }
.map { deviceList -> .map { deviceList ->
// If any key change, emit the userIds list // If any key change, emit the userIds list

View file

@ -317,6 +317,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// Todo this should be refactored and use same state as 4S section // Todo this should be refactored and use same state as 4S section
private fun refreshXSigningStatus() { private fun refreshXSigningStatus() {
lifecycleScope.launchWhenResumed {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys() val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
@ -340,9 +341,9 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled) mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
} }
} }
mCrossSigningStatePreference.isVisible = true mCrossSigningStatePreference.isVisible = true
} }
}
private val saveMegolmStartForActivityResult = registerStartForActivityResult { private val saveMegolmStartForActivityResult = registerStartForActivityResult {
val uri = it.data?.data ?: return@registerStartForActivityResult val uri = it.data?.data ?: return@registerStartForActivityResult

View file

@ -29,6 +29,7 @@ import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -41,7 +42,6 @@ import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -55,25 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) { ) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
init { init {
combine( observeCrossSigning()
session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningInfo(session.myUserId)
) { myDevicesInfo, mxCrossSigningInfo ->
myDevicesInfo to mxCrossSigningInfo
}
.execute { data ->
val crossSigningKeys = data.invoke()?.second?.getOrNull()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
copy(
crossSigningInfo = crossSigningKeys,
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
xSigningKeyCanSign = xSigningKeyCanSign
)
}
} }
var uiaContinuation: Continuation<UIABaseAuth>? = null var uiaContinuation: Continuation<UIABaseAuth>? = null
@ -90,7 +72,6 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null)) _viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
awaitCallback<Unit> {
session.cryptoService().crossSigningService().initializeCrossSigning( session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, override fun performStage(flowResponse: RegistrationFlowResponse,
@ -111,8 +92,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
uiaContinuation = promise uiaContinuation = promise
} }
} }
}, it) })
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
handleInitializeXSigningError(failure) handleInitializeXSigningError(failure)
} finally { } finally {
@ -149,6 +129,28 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
}.exhaustive }.exhaustive
} }
private fun observeCrossSigning() {
combine(
session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningInfo(session.myUserId)
) { myDevicesInfo, mxCrossSigningInfo ->
myDevicesInfo to mxCrossSigningInfo
}.onEach { data ->
val crossSigningKeys = data.second.getOrNull()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
setState {
copy(
crossSigningInfo = crossSigningKeys,
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
xSigningKeyCanSign = xSigningKeyCanSign
)
}
}
}
private fun handleInitializeXSigningError(failure: Throwable) { private fun handleInitializeXSigningError(failure: Throwable) {
Timber.e(failure, "## CrossSigning - Failed to initialize cross signing") Timber.e(failure, "## CrossSigning - Failed to initialize cross signing")
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing)))) _viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing))))

View file

@ -15,7 +15,6 @@
*/ */
package im.vector.app.features.settings.devices package im.vector.app.features.settings.devices
import com.airbnb.mvrx.Loading
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
@ -26,6 +25,7 @@ import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
@ -43,14 +43,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
by hiltMavericksViewModelFactory() by hiltMavericksViewModelFactory()
init { init {
initState()
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
)
}
session.flow().liveCrossSigningInfo(session.myUserId) session.flow().liveCrossSigningInfo(session.myUserId)
.execute { .execute {
copy( copy(
@ -78,10 +71,6 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
) )
} }
setState {
copy(deviceInfo = Loading())
}
session.flow().liveMyDevicesInfo() session.flow().liveMyDevicesInfo()
.map { devices -> .map { devices ->
devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId) devices.firstOrNull { it.deviceId == initialState.deviceId } ?: DeviceInfo(deviceId = initialState.deviceId)
@ -91,6 +80,21 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
} }
} }
private fun initState() {
viewModelScope.launch {
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
val isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
setState {
copy(
hasAccountCrossSigning = hasAccountCrossSigning,
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
isRecoverySetup = isRecoverySetup
)
}
}
}
override fun handle(action: EmptyAction) { override fun handle(action: EmptyAction) {
} }
} }

View file

@ -106,15 +106,7 @@ class DevicesViewModel @AssistedInject constructor(
private val refreshSource = PublishDataSource<Unit>() private val refreshSource = PublishDataSource<Unit>()
init { init {
initState()
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
myDeviceId = session.sessionParams.deviceId ?: ""
)
}
combine( combine(
session.flow().liveUserCryptoDevices(session.myUserId), session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo() session.flow().liveMyDevicesInfo()
@ -176,6 +168,21 @@ class DevicesViewModel @AssistedInject constructor(
queryRefreshDevicesList() queryRefreshDevicesList()
} }
private fun initState() {
viewModelScope.launch {
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
val myDeviceId = session.sessionParams.deviceId ?: ""
setState {
copy(
hasAccountCrossSigning = hasAccountCrossSigning,
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
myDeviceId = myDeviceId
)
}
}
}
override fun onCleared() { override fun onCleared() {
session.cryptoService().verificationService().removeListener(this) session.cryptoService().verificationService().removeListener(this)
super.onCleared() super.onCleared()