mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-26 19:08:44 +03:00
Suspend API: handle cross signing service
This commit is contained in:
parent
0590258d54
commit
046699bc84
16 changed files with 275 additions and 329 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,7 +219,6 @@ 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>) {
|
||||
|
@ -240,10 +238,8 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
},
|
||||
callback = it
|
||||
)
|
||||
Timber.d("Initialize cross signing SUCCESS")
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Failed to initialize cross signing")
|
||||
}
|
||||
|
|
|
@ -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,14 +140,19 @@ 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) {
|
||||
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
|
||||
if (event.isEncrypted()) {
|
||||
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
|
||||
|
@ -183,9 +189,6 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
|
||||
}
|
||||
}
|
||||
} else {
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -411,6 +411,7 @@ 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 ->
|
||||
coroutineScope.launch {
|
||||
if (session.cryptoService().crossSigningService().getMyCrossSigningKeys() == null ||
|
||||
session.cryptoService().crossSigningService().canCrossSign()) {
|
||||
(context as? AppCompatActivity)?.let {
|
||||
|
@ -421,6 +422,7 @@ class DefaultNavigator @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun open4SSetup(context: Context, setupMode: SetupMode) {
|
||||
if (context is AppCompatActivity) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -317,6 +317,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
|
||||
// Todo this should be refactored and use same state as 4S section
|
||||
private fun refreshXSigningStatus() {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||
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.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private val saveMegolmStartForActivityResult = registerStartForActivityResult {
|
||||
val uri = it.data?.data ?: return@registerStartForActivityResult
|
||||
|
|
|
@ -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,7 +72,6 @@ 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,
|
||||
|
@ -111,8 +92,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
|||
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))))
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue