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>> {
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow()
return session.cryptoService().getLiveCryptoDeviceInfo(userId)
.startWith(session.coroutineDispatchers.io) {
session.cryptoService().getCryptoDeviceInfo(userId)
}
}
fun liveCrossSigningInfo(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow()
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId)
.startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
}
}
fun liveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow()
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys()
.startWith(session.coroutineDispatchers.io) {
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
}

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.crypto
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.listeners.ProgressListener
@ -123,9 +124,9 @@ interface CryptoService {
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)

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
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
*/
fun isCrossSigningVerified(): Boolean
suspend fun isCrossSigningVerified(): Boolean
// TODO this isn't used anywhere besides in tests?
// 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.
* 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.
* Users needs to enter credentials
*/
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?,
callback: MatrixCallback<Unit>)
suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
/**
* 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.
*/
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
suspend fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
/**
* 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.
*/
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 */
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
/** 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?
@ -93,11 +93,11 @@ interface CrossSigningService {
fun allPrivateKeysKnown(): Boolean
/** 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>)
/** Mark our own master key as trusted */
fun markMyMasterKeyAsTrusted()
suspend fun markMyMasterKeyAsTrusted()
/**
* 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
* key of another user.
*/
fun checkDeviceTrust(otherUserId: String,
suspend fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
// TODO what is locallyTrusted used for?
locallyTrusted: Boolean?): DeviceTrustResult

View file

@ -25,6 +25,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
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))
}
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
return runBlocking {
this@DefaultCryptoService.olmMachine.getLiveDevices(userIds) // ?: LiveDevice(userIds, deviceObserver)
}
override fun getLiveCryptoDeviceInfo(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
return olmMachine.getLiveDevices(userIds)
}
/**

View file

@ -16,9 +16,11 @@
package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
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.withContext
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -64,7 +66,7 @@ import uniffi.olm.setLogger
import java.io.File
import java.nio.charset.Charset
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import uniffi.olm.OlmMachine as InnerMachine
import uniffi.olm.ProgressListener as RustProgressListener
import uniffi.olm.UserIdentity as RustUserIdentity
@ -81,98 +83,29 @@ private class CryptoProgressListener(private val listener: ProgressListener?) :
}
}
internal class LiveDevice(
internal var userIds: List<String>,
private var observer: DeviceUpdateObserver
) : MutableLiveData<List<CryptoDeviceInfo>>() {
private data class UserIdentityCollector(val userId: String, val collector: SendChannel<Optional<MXCrossSigningInfo>>) : SendChannel<Optional<MXCrossSigningInfo>> by collector
private data class DevicesCollector(val userIds: List<String>, val collector: SendChannel<List<CryptoDeviceInfo>>) : SendChannel<List<CryptoDeviceInfo>> by collector
private typealias PrivateKeysCollector = SendChannel<Optional<PrivateKeysInfo>>
override fun onActive() {
observer.addDeviceUpdateListener(this)
}
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)
}
private class FlowCollectors {
val userIdentityCollectors = CopyOnWriteArrayList<UserIdentityCollector>()
val privateKeyCollectors = CopyOnWriteArrayList<PrivateKeysCollector>()
val deviceCollectors = CopyOnWriteArrayList<DevicesCollector>()
}
fun setRustLogger() {
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(
user_id: String,
device_id: String,
path: File,
deviceObserver: DeviceUpdateObserver,
private val requestSender: RequestSender,
) {
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>()
private val flowCollectors = FlowCollectors()
/** Get our own user ID. */
fun userId(): String {
@ -193,26 +126,24 @@ internal class OlmMachine(
return this.inner
}
/** Update all of our live device listeners. */
private suspend fun updateLiveDevices() {
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
val devices = getCryptoDeviceInfo(users)
liveDevice.postValue(devices)
for (deviceCollector in flowCollectors.deviceCollectors) {
val devices = getCryptoDeviceInfo(deviceCollector.userIds)
deviceCollector.send(devices)
}
}
private suspend fun updateLiveUserIdentities() {
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) {
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
liveIdentity.postValue(identity)
for (userIdentityCollector in flowCollectors.userIdentityCollectors) {
val identity = getIdentity(userIdentityCollector.userId)?.toMxCrossSigningInfo()
userIdentityCollector.send(identity.toOptional())
}
}
private suspend fun updateLivePrivateKeys() {
val keys = this.exportCrossSigningKeys().toOptional()
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) {
liveKeys.postValue(keys)
for (privateKeyCollector in flowCollectors.privateKeyCollectors) {
privateKeyCollector.send(keys)
}
}
@ -712,20 +643,27 @@ internal class OlmMachine(
return getUserDevicesMap(userIds)
}
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver)
liveIdentity.value = identity
return liveIdentity
fun getLiveUserIdentity(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return channelFlow {
val userIdentityCollector = UserIdentityCollector(userId, this)
flowCollectors.userIdentityCollectors.add(userIdentityCollector)
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
send(identity)
awaitClose {
flowCollectors.userIdentityCollectors.remove(userIdentityCollector)
}
}
}
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> {
val keys = this.exportCrossSigningKeys().toOptional()
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver)
liveKeys.value = keys
return liveKeys
fun getLivePrivateCrossSigningKeys(): Flow<Optional<PrivateKeysInfo>> {
return channelFlow {
flowCollectors.privateKeyCollectors.add(this)
val keys = this@OlmMachine.exportCrossSigningKeys().toOptional()
send(keys)
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.
*/
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
val plainDevices = getCryptoDeviceInfo(userIds)
val devices = LiveDevice(userIds, deviceUpdateObserver)
devices.value = plainDevices
return devices
fun getLiveDevices(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
return channelFlow {
val devicesCollector = DevicesCollector(userIds, this)
flowCollectors.deviceCollectors.add(devicesCollector)
val devices = getCryptoDeviceInfo(userIds)
send(devices)
awaitClose {
flowCollectors.deviceCollectors.remove(devicesCollector)
}
}
}
/** 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
) {
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, requestSender)
var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, requestSender)
}

View file

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.crypto
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.NoOpMatrixCallback
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
*/
override fun isCrossSigningVerified(): Boolean {
return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) {
override suspend fun isCrossSigningVerified(): Boolean {
return when (val identity = olmMachine.getIdentity(olmMachine.userId()) ) {
is OwnUserIdentity -> identity.trustsOurOwnDevice()
else -> false
}
}
override fun isUserTrusted(otherUserId: String): Boolean {
override suspend fun isUserTrusted(otherUserId: String): Boolean {
// This seems to be used only in tests.
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.
* Checks that my trusted user key has signed the other user UserKey
*/
override fun checkUserTrust(otherUserId: String): UserTrustResult {
val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }
override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
val identity = olmMachine.getIdentity(olmMachine.userId())
// While UserTrustResult has many different states, they are by the callers
// converted to a boolean value immediately, thus we don't need to support
// all the values.
return if (identity != null) {
val verified = runBlocking { identity.verified() }
val verified = identity.verified()
if (verified) {
UserTrustResult.Success
@ -84,8 +84,8 @@ internal class RustCrossSigningService @Inject constructor(
* Initialize cross signing for this user.
* Users needs to enter credentials
*/
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) }
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
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.
*/
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() }
override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo()
}
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
return runBlocking { olmMachine.getLiveUserIdentity(userId) }
override fun getLiveCrossSigningKeys(userId: String): Flow<Optional<MXCrossSigningInfo>> {
return olmMachine.getLiveUserIdentity(userId)
}
/** Get our own public cross signing keys */
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return getUserCrossSigningKeys(olmMachine.userId())
}
/** Get our own private cross signing keys */
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return runBlocking { olmMachine.exportCrossSigningKeys() }
override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return olmMachine.exportCrossSigningKeys()
}
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() }
override fun getLiveCrossSigningPrivateKeys(): Flow<Optional<PrivateKeysInfo>> {
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 */
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
override suspend fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
// This is only used in a test
val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) }
val userIdentity = olmMachine.getIdentity(otherUserId)
if (userIdentity != null) {
runBlocking { userIdentity.verify() }
userIdentity.verify()
callback.onSuccess(Unit)
} else {
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 */
override fun markMyMasterKeyAsTrusted() {
override suspend fun markMyMasterKeyAsTrusted() {
// This doesn't seem to be used?
this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback())
}
@ -171,10 +171,8 @@ internal class RustCrossSigningService @Inject constructor(
*/
override suspend fun trustDevice(deviceId: String) {
val device = olmMachine.getDevice(olmMachine.userId(), deviceId)
return if (device != null) {
if (device != null) {
val verified = device.verify()
if (verified) {
return
} 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
* key of another user.
*/
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) }
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val device = olmMachine.getDevice(otherUserId, otherDeviceId)
return if (device != null) {
// TODO i don't quite understand the semantics here and there are no docs for
// DeviceTrustResult, what do the different result types mean exactly,
// 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?
DeviceTrustResult.Success(runBlocking { device.trustLevel() })
DeviceTrustResult.Success(device.trustLevel())
} else {
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.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@ -91,12 +90,7 @@ class BootstrapCrossSigningTask @Inject constructor(
)
try {
awaitCallback<Unit> {
crossSigningService.initializeCrossSigning(
params.userInteractiveAuthInterceptor,
it
)
}
crossSigningService.initializeCrossSigning(params.userInteractiveAuthInterceptor)
if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
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.util.toMatrixItem
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -220,30 +219,27 @@ class HomeActivityViewModel @AssistedInject constructor(
// Try to initialize cross signing in background if possible
Timber.d("Initialize cross signing...")
try {
awaitCallback<Unit> {
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
// We missed server grace period or it's not setup, see if we remember locally password
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
errCode == null &&
reAuthHelper.data != null) {
promise.resume(
UserPasswordAuth(
session = flowResponse.session,
user = session.myUserId,
password = reAuthHelper.data
)
)
} else {
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
}
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
// We missed server grace period or it's not setup, see if we remember locally password
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
errCode == null &&
reAuthHelper.data != null) {
promise.resume(
UserPasswordAuth(
session = flowResponse.session,
user = session.myUserId,
password = reAuthHelper.data
)
)
} else {
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
}
},
callback = it
)
Timber.d("Initialize cross signing SUCCESS")
}
}
},
)
Timber.d("Initialize cross signing SUCCESS")
} catch (failure: Throwable) {
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.SendStateDecoration
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.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
@ -139,52 +140,54 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
}
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration {
return if (
event.root.sendState == SendState.SYNCED &&
roomSummary?.isEncrypted.orFalse() &&
// is user verified
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true) {
val ts = roomSummary?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0
if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
E2EDecoration.NONE
} else {
val sendingDevice = event.root.content
.toModel<EncryptedEventContent>()
?.deviceId
?.let { deviceId ->
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
}
when {
sendingDevice == null -> {
// For now do not decorate this with warning
// maybe it's a deleted session
E2EDecoration.NONE
}
sendingDevice.trustLevel == null -> {
E2EDecoration.WARN_SENT_BY_UNKNOWN
}
sendingDevice.trustLevel?.isVerified().orFalse() -> {
E2EDecoration.NONE
}
else -> {
E2EDecoration.WARN_SENT_BY_UNVERIFIED
}
}
}
if (event.root.sendState != SendState.SYNCED)
return E2EDecoration.NONE
if (!roomSummary?.isEncrypted.orFalse())
return E2EDecoration.NONE
val isUserVerified = runBlocking {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
}
if (!isUserVerified) {
return E2EDecoration.NONE
}
val ts = roomSummary?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0
return if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
E2EDecoration.NONE
} else {
if (event.root.isStateEvent()) {
// Do not warn for state event, they are always in clear
E2EDecoration.NONE
} else {
// If event is in clear after the room enabled encryption we should warn
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
val sendingDevice = event.root.content
.toModel<EncryptedEventContent>()
?.deviceId
?.let { deviceId ->
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
}
when {
sendingDevice == null -> {
// For now do not decorate this with warning
// maybe it's a deleted session
E2EDecoration.NONE
}
sendingDevice.trustLevel == null -> {
E2EDecoration.WARN_SENT_BY_UNKNOWN
}
sendingDevice.trustLevel?.isVerified().orFalse() -> {
E2EDecoration.NONE
}
else -> {
E2EDecoration.WARN_SENT_BY_UNVERIFIED
}
}
}
} else {
E2EDecoration.NONE
if (event.root.isStateEvent()) {
// Do not warn for state event, they are always in clear
E2EDecoration.NONE
} else {
// If event is in clear after the room enabled encryption we should warn
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
}
}
}

View file

@ -411,13 +411,15 @@ class DefaultNavigator @Inject constructor(
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
sessionHolder.getSafeActiveSession()?.let { session ->
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
session.cryptoService().crossSigningService().canCrossSign()) {
(context as? AppCompatActivity)?.let {
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
coroutineScope.launch {
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
session.cryptoService().crossSigningService().canCrossSign()) {
(context as? AppCompatActivity)?.let {
BootstrapBottomSheet.show(it.supportFragmentManager, SetupMode.NORMAL)
}
} else {
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
}
} else {
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
}
}
}

View file

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

View file

@ -317,31 +317,32 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// Todo this should be refactored and use same state as 4S section
private fun refreshXSigningStatus() {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
lifecycleScope.launchWhenResumed {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
when {
xSigningKeyCanSign -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
}
xSigningKeysAreTrusted -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
}
xSigningIsEnableInAccount -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
}
else -> {
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
when {
xSigningKeyCanSign -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
}
xSigningKeysAreTrusted -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
}
xSigningIsEnableInAccount -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
}
else -> {
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
}
}
mCrossSigningStatePreference.isVisible = true
}
mCrossSigningStatePreference.isVisible = true
}
private val saveMegolmStartForActivityResult = registerStartForActivityResult {

View file

@ -29,6 +29,7 @@ import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.login.ReAuthHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth
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.isVerified
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -55,25 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
) : VectorViewModel<CrossSigningSettingsViewState, CrossSigningSettingsAction, CrossSigningSettingsViewEvents>(initialState) {
init {
combine(
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
)
}
observeCrossSigning()
}
var uiaContinuation: Continuation<UIABaseAuth>? = null
@ -90,29 +72,27 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
_viewEvents.post(CrossSigningSettingsViewEvents.ShowModalWaitingView(null))
viewModelScope.launch(Dispatchers.IO) {
try {
awaitCallback<Unit> {
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse,
errCode: String?,
promise: Continuation<UIABaseAuth>) {
Timber.d("## UIA : initializeCrossSigning UIA")
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
reAuthHelper.data != null && errCode == null) {
UserPasswordAuth(
session = null,
user = session.myUserId,
password = reAuthHelper.data
).let { promise.resume(it) }
} else {
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
session.cryptoService().crossSigningService().initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse,
errCode: String?,
promise: Continuation<UIABaseAuth>) {
Timber.d("## UIA : initializeCrossSigning UIA")
if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD &&
reAuthHelper.data != null && errCode == null) {
UserPasswordAuth(
session = null,
user = session.myUserId,
password = reAuthHelper.data
).let { promise.resume(it) }
} else {
Timber.d("## UIA : initializeCrossSigning UIA > start reauth activity")
_viewEvents.post(CrossSigningSettingsViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
}, it)
}
}
})
} catch (failure: Throwable) {
handleInitializeXSigningError(failure)
} finally {
@ -149,6 +129,28 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
}.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) {
Timber.e(failure, "## CrossSigning - 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
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
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.VectorViewModel
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
@ -43,14 +43,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
by hiltMavericksViewModelFactory()
init {
setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup()
)
}
initState()
session.flow().liveCrossSigningInfo(session.myUserId)
.execute {
copy(
@ -78,10 +71,6 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
)
}
setState {
copy(deviceInfo = Loading())
}
session.flow().liveMyDevicesInfo()
.map { devices ->
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) {
}
}

View file

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