From f209ae26bcf94c28eee36845552a1b07da42eda7 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 29 Oct 2021 15:24:18 +0200 Subject: [PATCH] Wire compute room shields with rust --- .gitignore | 2 + .../sdk/api/session/crypto/CryptoService.kt | 9 +- .../crosssigning/CrossSigningService.kt | 3 + .../sdk/internal/crypto/CryptoModule.kt | 11 +- .../crypto/CryptoSessionInfoProvider.kt | 50 +- .../internal/crypto/DefaultCryptoService.kt | 394 +--- .../android/sdk/internal/crypto/Device.kt | 2 +- .../sdk/internal/crypto/DeviceListManager.kt | 1 + .../android/sdk/internal/crypto/OlmMachine.kt | 12 +- .../sdk/internal/crypto/OlmMachineProvider.kt | 37 + .../sdk/internal/crypto/QrCodeVerification.kt | 2 +- .../sdk/internal/crypto/RequestSender.kt | 282 +++ .../crypto/RustCrossSigningService.kt | 59 +- .../sdk/internal/crypto/SasVerification.kt | 2 +- .../sdk/internal/crypto/UserIdentities.kt | 11 +- .../internal/crypto/VerificationRequest.kt | 2 +- .../sdk/internal/crypto/api/CryptoApi.kt | 1 - .../DefaultCrossSigningService.kt | 1602 ++++++++--------- .../crypto/crosssigning/UpdateTrustWorker.kt | 736 ++++---- .../crypto/model/rest/KeysClaimResponse.kt | 4 +- .../ClaimOneTimeKeysForUsersDeviceTask.kt | 3 - .../internal/crypto/tasks/UploadKeysTask.kt | 2 - .../verification/RustVerificationService.kt | 72 +- .../crypto/verification/UpdateDispatcher.kt | 78 + .../database/mapper/RoomSummaryMapper.kt | 2 +- .../sdk/internal/session/SessionComponent.kt | 6 +- .../room/create/CreateRoomBodyBuilder.kt | 16 +- .../room/membership/LoadRoomMembersTask.kt | 10 +- .../room/summary/RoomSummaryUpdater.kt | 11 +- rust-sdk/Makefile | 18 +- 30 files changed, 1867 insertions(+), 1573 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachineProvider.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/UpdateDispatcher.kt diff --git a/.gitignore b/.gitignore index 2661fdd7fe..d4f9f385ac 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ Cargo.lock /fastlane/report.xml /library/build + +matrix-sdk-android/src/main/jniLibs/ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 7d9bc33ba3..fc53540c5f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -35,13 +35,11 @@ import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody interface CryptoService { @@ -153,4 +151,11 @@ interface CryptoService { * send, in order to speed up sending of the message. */ fun prepareToEncrypt(roomId: String, callback: MatrixCallback) + + /** + * When LL all room members might not be loaded when setting up encryption. + * This is called after room members have been loaded + * ... not sure if shoud be API + */ + fun onE2ERoomMemberLoadedFromServer(roomId: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt index 709babc23f..c226e4aa92 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult @@ -104,6 +105,8 @@ interface CrossSigningService { fun trustDevice(deviceId: String, callback: MatrixCallback) + suspend fun shieldForGroup(userIds: List) : RoomEncryptionTrustLevel + /** * Check if a device is trusted * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index fe388b44e2..eb898188ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -27,7 +27,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningServic import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.crosssigning.ComputeTrustTask import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultComputeTrustTask -import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask @@ -96,6 +95,11 @@ import org.matrix.android.sdk.internal.di.UserMd5 import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.cache.ClearCacheTask import org.matrix.android.sdk.internal.session.cache.RealmClearCacheTask +import io.realm.RealmConfiguration +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import org.matrix.android.sdk.api.session.crypto.verification.VerificationService +import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService import retrofit2.Retrofit import java.io.File @@ -238,7 +242,10 @@ internal abstract class CryptoModule { abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask @Binds - abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService + abstract fun bindCrossSigningService(service: RustCrossSigningService): CrossSigningService + + @Binds + abstract fun bindVerificationService(service: RustVerificationService): VerificationService @Binds abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index 5338e7e92f..b80bdc4537 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -17,13 +17,21 @@ package org.matrix.android.sdk.internal.crypto import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.util.fetchCopied +import org.matrix.android.sdk.internal.util.logLimit +import timber.log.Timber import javax.inject.Inject /** @@ -31,7 +39,8 @@ import javax.inject.Inject * in the session DB, this class encapsulate this functionality */ internal class CryptoSessionInfoProvider @Inject constructor( - @SessionDatabase private val monarchy: Monarchy + @SessionDatabase private val monarchy: Monarchy, + @UserId private val myUserId: String ) { fun isRoomEncrypted(roomId: String): Boolean { @@ -47,7 +56,7 @@ internal class CryptoSessionInfoProvider @Inject constructor( /** * @param allActive if true return joined as well as invited, if false, only joined */ - fun getRoomUserIds(roomId: String, allActive: Boolean): List { + fun getRoomUserIds(roomId: String, allActive: Boolean): List { var userIds: List = emptyList() monarchy.doWithRealm { realm -> userIds = if (allActive) { @@ -58,4 +67,41 @@ internal class CryptoSessionInfoProvider @Inject constructor( } return userIds } + + fun getUserListForShieldComputation(roomId: String): List { + var userIds: List = emptyList() + monarchy.doWithRealm { realm -> + userIds = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() + } + var isDirect = false + monarchy.doWithRealm { realm -> + isDirect = RoomSummaryEntity.where(realm, roomId = roomId).findFirst()?.isDirect == true + } + + return if (isDirect || userIds.size <= 2) { + userIds.filter { it != myUserId } + } else { + userIds + } + } + + fun getRoomsWhereUsersAreParticipating(userList: List): List { + var roomIds: List? = null + monarchy.doWithRealm { sessionRealm -> + roomIds = sessionRealm.where(RoomMemberSummaryEntity::class.java) + .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) + .distinct(RoomMemberSummaryEntityFields.ROOM_ID) + .findAll() + .map { it.roomId } + .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } + } + return roomIds.orEmpty() + } + + fun updateShieldForRoom(roomId: String, shield: RoomEncryptionTrustLevel) { + monarchy.writeAsync { realm -> + val summary = RoomSummaryEntity.where(realm, roomId = roomId).findFirst() + summary?.roomEncryptionTrustLevel = shield + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 6d1d4a9935..021b4ff42d 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -88,31 +88,17 @@ import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse -import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse -import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse -import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask -import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask -import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask -import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService import org.matrix.android.sdk.internal.di.DeviceId -import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.network.parsing.CheckNumberType import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -120,235 +106,13 @@ import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import timber.log.Timber -import uniffi.olm.OutgoingVerificationRequest import uniffi.olm.Request import uniffi.olm.RequestType -import uniffi.olm.SignatureUploadRequest -import uniffi.olm.UploadSigningKeysRequest -import java.io.File import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.math.max -internal class RequestSender @Inject constructor( - private val sendToDeviceTask: SendToDeviceTask, - private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask, - private val uploadKeysTask: UploadKeysTask, - private val downloadKeysForUsersTask: DownloadKeysForUsersTask, - private val signaturesUploadTask: UploadSignaturesTask, - private val sendVerificationMessageTask: Lazy, - private val uploadSigningKeysTask: UploadSigningKeysTask, - private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask, - private val getKeysBackupVersionTask: GetKeysBackupVersionTask, - private val deleteBackupTask: DeleteBackupTask, - private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, - private val backupRoomKeysTask: StoreSessionsDataTask, - private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, - private val getSessionsDataTask: GetSessionsDataTask, - private val getRoomSessionsDataTask: GetRoomSessionsDataTask, - private val getRoomSessionDataTask: GetRoomSessionDataTask, - ) { - companion object { - const val REQUEST_RETRY_COUNT = 3 - } - - suspend fun claimKeys(request: Request.KeysClaim): String { - val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(request.oneTimeKeys) - val response = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, REQUEST_RETRY_COUNT) - val adapter = MoshiProvider - .providesMoshi() - .adapter(KeysClaimResponse::class.java) - return adapter.toJson(response)!! - } - - suspend fun queryKeys(request: Request.KeysQuery): String { - val params = DownloadKeysForUsersTask.Params(request.users, null) - val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT) - val adapter = MoshiProvider.providesMoshi().adapter(KeysQueryResponse::class.java) - return adapter.toJson(response)!! - } - - suspend fun uploadKeys(request: Request.KeysUpload): String { - val body = MoshiProvider.providesMoshi().adapter(Map::class.java).fromJson(request.body)!! - val params = UploadKeysTask.Params(body) - - val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) - val adapter = MoshiProvider.providesMoshi().adapter(KeysUploadResponse::class.java) - - return adapter.toJson(response)!! - } - - suspend fun sendVerificationRequest(request: OutgoingVerificationRequest) { - when (request) { - is OutgoingVerificationRequest.InRoom -> sendRoomMessage(request) - is OutgoingVerificationRequest.ToDevice -> sendToDevice(request) - } - } - - suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): String { - return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId) - } - - suspend fun sendRoomMessage(request: Request.RoomMessage): String { - return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId) - } - - suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): String { - val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) - val jsonContent = adapter.fromJson(content) - val event = Event(eventType, transactionId, jsonContent, roomId = roomId) - val params = SendVerificationMessageTask.Params(event) - return this.sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT) - } - - suspend fun sendSignatureUpload(request: Request.SignatureUpload) { - sendSignatureUpload(request.body) - } - - suspend fun sendSignatureUpload(request: SignatureUploadRequest) { - sendSignatureUpload(request.body) - } - - private suspend fun sendSignatureUpload(body: String) { - val adapter = MoshiProvider.providesMoshi().adapter>>(Map::class.java) - val signatures = adapter.fromJson(body)!! - val params = UploadSignaturesTask.Params(signatures) - this.signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT) - } - - suspend fun uploadCrossSigningKeys( - request: UploadSigningKeysRequest, - interactiveAuthInterceptor: UserInteractiveAuthInterceptor? - ) { - val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java) - val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel() - val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel() - val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel() - - val uploadSigningKeysParams = UploadSigningKeysTask.Params( - masterKey, - userSigningKey, - selfSigningKey, - null - ) - - try { - uploadSigningKeysTask.execute(uploadSigningKeysParams) - } catch (failure: Throwable) { - if (interactiveAuthInterceptor == null - || !handleUIA( - failure = failure, - interceptor = interactiveAuthInterceptor, - retryBlock = { authUpdate -> - uploadSigningKeysTask.executeRetry( - uploadSigningKeysParams.copy(userAuthParam = authUpdate), - REQUEST_RETRY_COUNT - ) - } - ) - ) { - Timber.d("## UIA: propagate failure") - throw failure - } - } - } - - suspend fun sendToDevice(request: Request.ToDevice) { - sendToDevice(request.eventType, request.body, request.requestId) - } - - suspend fun sendToDevice(request: OutgoingVerificationRequest.ToDevice) { - sendToDevice(request.eventType, request.body, request.requestId) - } - - suspend fun sendToDevice(eventType: String, body: String, transactionId: String) { - val adapter = MoshiProvider - .providesMoshi() - .newBuilder() - .add(CheckNumberType.JSON_ADAPTER_FACTORY) - .build() - .adapter>>(Map::class.java) - val jsonBody = adapter.fromJson(body)!! - - val userMap = MXUsersDevicesMap() - userMap.join(jsonBody) - - val sendToDeviceParams = SendToDeviceTask.Params(eventType, userMap, transactionId) - sendToDeviceTask.executeRetry(sendToDeviceParams, REQUEST_RETRY_COUNT) - } - - suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? { - return try { - if (version != null) { - getKeysBackupVersionTask.execute(version) - } else { - getKeysBackupLastVersionTask.execute(Unit) - } - } catch (failure: Throwable) { - if (failure is Failure.ServerError - && failure.error.code == MatrixError.M_NOT_FOUND) { - null - } else { - throw failure - } - } - } - - suspend fun createKeyBackup(body: CreateKeysBackupVersionBody): KeysVersion { - return createKeysBackupVersionTask.execute(body) - } - - suspend fun deleteKeyBackup(version: String) { - val params = DeleteBackupTask.Params(version) - deleteBackupTask.execute(params) - } - - suspend fun backupRoomKeys(request: Request.KeysBackup): String { - val adapter = MoshiProvider - .providesMoshi() - .newBuilder() - .add(CheckNumberType.JSON_ADAPTER_FACTORY) - .build() - .adapter>( - Types.newParameterizedType( - Map::class.java, - String::class.java, - RoomKeysBackupData::class.java - )) - val keys = adapter.fromJson(request.rooms)!! - val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys)) - val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) - val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java) - return responseAdapter.toJson(response)!! - } - - suspend fun updateBackup(keysBackupVersion: KeysVersionResult, body: UpdateKeysBackupVersionBody) { - val params = UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, body) - updateKeysBackupVersionTask.executeRetry(params, REQUEST_RETRY_COUNT) - } - - suspend fun downloadBackedUpKeys(version: String, roomId: String, sessionId: String): KeysBackupData { - val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version)) - - return KeysBackupData(mutableMapOf( - roomId to RoomKeysBackupData(mutableMapOf( - sessionId to data - )) - )) - } - - suspend fun downloadBackedUpKeys(version: String, roomId: String): KeysBackupData { - val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version)) - // Convert to KeysBackupData - return KeysBackupData(mutableMapOf(roomId to data)) - } - - suspend fun downloadBackedUpKeys(version: String): KeysBackupData { - return getSessionsDataTask.execute(GetSessionsDataTask.Params(version)) - } -} - /** * A `CryptoService` class instance manages the end-to-end crypto for a session. * @@ -365,8 +129,10 @@ internal class DefaultCryptoService @Inject constructor( private val userId: String, @DeviceId private val deviceId: String?, - @SessionFilesDirectory - private val dataDir: File, +// @SessionId +// private val sessionId: String, +// @SessionFilesDirectory +// private val dataDir: File, // the crypto store private val cryptoStore: IMXCryptoStore, // Set of parameters used to configure/customize the end-to-end crypto. @@ -384,18 +150,20 @@ internal class DefaultCryptoService @Inject constructor( private val taskExecutor: TaskExecutor, private val cryptoCoroutineScope: CoroutineScope, private val sender: RequestSender, + private val crossSigningService: CrossSigningService, + private val verificationService: RustVerificationService, + private val olmMachineProvider: OlmMachineProvider ) : CryptoService { private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - private var olmMachine: OlmMachine? = null + private val olmMachine by lazy { olmMachineProvider.olmMachine } // The verification service. - private var verificationService: RustVerificationService? = null +// private var verificationService: RustVerificationService? = null - // The cross signing service. - private var crossSigningService: RustCrossSigningService? = null +// private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver() // The key backup service. private var keysBackupService: RustKeyBackupService? = null @@ -410,7 +178,7 @@ internal class DefaultCryptoService @Inject constructor( // TODO does this need to be concurrent? private val newSessionListeners = ArrayList() - suspend fun onStateEvent(roomId: String, event: Event) { + fun onStateEvent(roomId: String, event: Event) { when (event.getClearType()) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) @@ -418,12 +186,14 @@ internal class DefaultCryptoService @Inject constructor( } } - suspend fun onLiveEvent(roomId: String, event: Event) { + fun onLiveEvent(roomId: String, event: Event) { when (event.getClearType()) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) - else -> this.verificationService?.onEvent(event) + else -> cryptoCoroutineScope.launch { + this@DefaultCryptoService.verificationService?.onEvent(event) + } } } @@ -463,7 +233,7 @@ internal class DefaultCryptoService @Inject constructor( } override fun getMyDevice(): CryptoDeviceInfo { - return runBlocking { olmMachine!!.ownDevice() } + return runBlocking { olmMachine.ownDevice() } } override fun fetchDevicesList(callback: MatrixCallback) { @@ -565,14 +335,10 @@ internal class DefaultCryptoService @Inject constructor( try { setRustLogger() - val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, sender) - olmMachine = machine - verificationService = RustVerificationService(machine) - crossSigningService = RustCrossSigningService(machine) keysBackupService = RustKeyBackupService(machine, sender, coroutineDispatchers, cryptoCoroutineScope) Timber.v( "## CRYPTO | Successfully started up an Olm machine for " + - "${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}") + "${userId}, ${deviceId}, identity keys: ${this.olmMachine.identityKeys()}") } catch (throwable: Throwable) { Timber.v("Failed create an Olm machine: $throwable") } @@ -614,38 +380,9 @@ internal class DefaultCryptoService @Inject constructor( /** * @return the VerificationService */ - override fun verificationService(): VerificationService { - // TODO yet another problem because the CryptoService is started in the - // sync loop - // - // The `KeyRequestHandler` and `IncomingVerificationHandler` want to add - // listeners to the verification service, they are initialized in the - // `ActiveSessionHolder` class in the `setActiveSession()` method. In - // the `setActiveSession()` method we call the `start()` method of the - // handlers without first calling the `start()` method of the - // `DefaultCryptoService`. - // - // The start method of the crypto service isn't part of the - // `CryptoService` interface so it currently can't be called there. I'm - // inclined to believe that it should be, and that it should be - // initialized before anything else tries to do something with it. - // - // Let's initialize here as a workaround until we figure out if the - // above conclusion is correct. - if (verificationService == null) { - internalStart() - } + override fun verificationService() = verificationService - return verificationService!! - } - - override fun crossSigningService(): CrossSigningService { - if (crossSigningService == null) { - internalStart() - } - - return crossSigningService!! - } + override fun crossSigningService() = crossSigningService /** * A sync response has been received @@ -685,7 +422,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { runBlocking { - this@DefaultCryptoService.olmMachine?.getCryptoDeviceInfo(userId, deviceId) + this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId, deviceId) } } else { null @@ -694,7 +431,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getCryptoDeviceInfo(userId: String): List { return runBlocking { - this@DefaultCryptoService.olmMachine?.getCryptoDeviceInfo(userId) ?: listOf() + this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId) ?: listOf() } } @@ -704,7 +441,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getLiveCryptoDeviceInfo(userIds: List): LiveData> { return runBlocking { - this@DefaultCryptoService.olmMachine?.getLiveDevices(userIds) ?: LiveDevice(userIds, deviceObserver) + this@DefaultCryptoService.olmMachine.getLiveDevices(userIds) //?: LiveDevice(userIds, deviceObserver) } } @@ -755,7 +492,7 @@ internal class DefaultCryptoService @Inject constructor( Timber.v("Enabling encryption in $roomId for the first time; invalidating device lists for all users therein") val userIds = ArrayList(membersId) - olmMachine!!.updateTrackedUsers(userIds) + olmMachine.updateTrackedUsers(userIds) } return true @@ -833,7 +570,7 @@ internal class DefaultCryptoService @Inject constructor( } override fun discardOutboundSession(roomId: String) { - olmMachine?.discardRoomKey(roomId) + olmMachine.discardRoomKey(roomId) } /** @@ -846,7 +583,7 @@ internal class DefaultCryptoService @Inject constructor( @Throws(MXCryptoError::class) override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { return runBlocking { - olmMachine!!.decryptRoomEvent(event) + olmMachine.decryptRoomEvent(event) } } @@ -873,20 +610,29 @@ internal class DefaultCryptoService @Inject constructor( Timber.w("Invalid encryption event") return } + + // Do not load members here, would defeat lazy loading cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - val params = LoadRoomMembersTask.Params(roomId) - try { - loadRoomMembersTask.execute(params) - } catch (throwable: Throwable) { - Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") - } finally { - val userIds = getRoomUserIds(roomId) - olmMachine!!.updateTrackedUsers(userIds) - setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds) - } +// val params = LoadRoomMembersTask.Params(roomId) +// try { +// loadRoomMembersTask.execute(params) +// } catch (throwable: Throwable) { +// Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") +// } finally { + val userIds = getRoomUserIds(roomId) + olmMachine.updateTrackedUsers(userIds) + setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), userIds) +// } } } + override fun onE2ERoomMemberLoadedFromServer(roomId: String) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + val userIds = getRoomUserIds(roomId) + // Because of LL we might want to update tracked users + olmMachine.updateTrackedUsers(userIds) + } + } private fun getRoomUserIds(roomId: String): List { val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() && shouldEncryptForInvitedMembers(roomId) @@ -898,7 +644,7 @@ internal class DefaultCryptoService @Inject constructor( * * @param event the membership event causing the change */ - private suspend fun onRoomMembershipEvent(roomId: String, event: Event) { + private fun onRoomMembershipEvent(roomId: String, event: Event) { // We only care about the memberships if this room is encrypted if (isRoomEncrypted(roomId)) { return @@ -909,7 +655,9 @@ internal class DefaultCryptoService @Inject constructor( val membership = roomMember?.membership if (membership == Membership.JOIN) { // make sure we are tracking the deviceList for this user. - olmMachine!!.updateTrackedUsers(listOf(userId)) + cryptoCoroutineScope.launch { + olmMachine.updateTrackedUsers(listOf(userId)) + } } else if (membership == Membership.INVITE && shouldEncryptForInvitedMembers(roomId) && isEncryptionEnabledForInvitedUser()) { @@ -918,7 +666,11 @@ internal class DefaultCryptoService @Inject constructor( // know what other servers are in the room at the time they've been invited. // They therefore will not send device updates if a user logs in whilst // their state is invite. - olmMachine!!.updateTrackedUsers(listOf(userId)) + cryptoCoroutineScope.launch { + olmMachine.updateTrackedUsers(listOf(userId)) + } + } else { + // nop } } } @@ -952,7 +704,7 @@ internal class DefaultCryptoService @Inject constructor( deviceChanges: DeviceListResponse?, keyCounts: DeviceOneTimeKeysCountSyncResponse?) { // Decrypt and handle our to-device events - val toDeviceEvents = this.olmMachine!!.receiveSyncChanges(toDevice, deviceChanges, keyCounts) + val toDeviceEvents = this.olmMachine.receiveSyncChanges(toDevice, deviceChanges, keyCounts) // Notify the our listeners about room keys so decryption is retried. if (toDeviceEvents.events != null) { @@ -981,7 +733,7 @@ internal class DefaultCryptoService @Inject constructor( this.keysBackupService?.onSecretKeyGossip(secretContent.secretValue) } else -> { - this.verificationService?.onEvent(event) + this.verificationService.onEvent(event) } } } @@ -990,7 +742,7 @@ internal class DefaultCryptoService @Inject constructor( private suspend fun preshareRoomKey(roomId: String, roomMembers: List) { keyClaimLock.withLock { - val request = this.olmMachine!!.getMissingSessions(roomMembers) + val request = this.olmMachine.getMissingSessions(roomMembers) // This request can only be a keys claim request. if (request != null) { when (request) { @@ -1008,7 +760,7 @@ internal class DefaultCryptoService @Inject constructor( keyShareLock.withLock { coroutineScope { - this@DefaultCryptoService.olmMachine!!.shareRoomKey(roomId, roomMembers).map { + this@DefaultCryptoService.olmMachine.shareRoomKey(roomId, roomMembers).map { when (it) { is Request.ToDevice -> { sharedKey = true @@ -1035,18 +787,28 @@ internal class DefaultCryptoService @Inject constructor( } private suspend fun encrypt(roomId: String, eventType: String, content: Content): Content { - return olmMachine!!.encrypt(roomId, eventType, content) + return olmMachine.encrypt(roomId, eventType, content) } private suspend fun uploadKeys(request: Request.KeysUpload) { val response = this.sender.uploadKeys(request) - this.olmMachine!!.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response) + this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response) } private suspend fun queryKeys(request: Request.KeysQuery) { try { val response = this.sender.queryKeys(request) - this.olmMachine!!.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response) + this.olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response) + + // Update the shields! + cryptoCoroutineScope.launch { + cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(request.users).forEach { roomId -> + val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId) + val shield = crossSigningService.shieldForGroup(userGroup) + cryptoSessionInfoProvider.updateShieldForRoom(roomId, shield) + } + } + } catch (throwable: Throwable) { Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") } @@ -1054,23 +816,23 @@ internal class DefaultCryptoService @Inject constructor( private suspend fun sendToDevice(request: Request.ToDevice) { this.sender.sendToDevice(request) - olmMachine!!.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}") + olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}") } private suspend fun claimKeys(request: Request.KeysClaim) { val response = this.sender.claimKeys(request) - olmMachine!!.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response) + olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response) } private suspend fun signatureUpload(request: Request.SignatureUpload) { this.sender.sendSignatureUpload(request) - olmMachine!!.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}") + olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}") } private suspend fun sendOutgoingRequests() { outgoingRequestsLock.withLock { coroutineScope { - olmMachine!!.outgoingRequests().map { + olmMachine.outgoingRequests().map { when (it) { is Request.KeysUpload -> { async { @@ -1122,7 +884,7 @@ internal class DefaultCryptoService @Inject constructor( */ override suspend fun exportRoomKeys(password: String): ByteArray { val iterationCount = max(10000, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) - return olmMachine!!.exportKeys(password, iterationCount) + return olmMachine.exportKeys(password, iterationCount) } /** @@ -1234,7 +996,7 @@ internal class DefaultCryptoService @Inject constructor( */ override fun reRequestRoomKeyForEvent(event: Event) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - val requestPair = olmMachine!!.requestRoomKey(event) + val requestPair = olmMachine.requestRoomKey(event) val cancellation = requestPair.cancellation val request = requestPair.keyRequest @@ -1279,9 +1041,9 @@ internal class DefaultCryptoService @Inject constructor( if (forceDownload) { // TODO replicate the logic from the device list manager // where we would download the fresh info from the server. - this@DefaultCryptoService.olmMachine?.getUserDevicesMap(userIds) ?: MXUsersDevicesMap() + this@DefaultCryptoService.olmMachine.getUserDevicesMap(userIds) // ?: MXUsersDevicesMap() } else { - this@DefaultCryptoService.olmMachine?.getUserDevicesMap(userIds) ?: MXUsersDevicesMap() + this@DefaultCryptoService.olmMachine.getUserDevicesMap(userIds) //?: MXUsersDevicesMap() } }.foldToCallback(callback) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt index e1db5ddbe9..dfe1994d9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt @@ -39,7 +39,7 @@ internal class Device( private val machine: OlmMachine, private var inner: InnerDevice, private val sender: RequestSender, - private val listeners: ArrayList, + private val listeners: ArrayList ) { @Throws(CryptoStoreException::class) private suspend fun refreshData() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 494e6d7cc7..9493dddecc 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -35,6 +35,7 @@ import timber.log.Timber import javax.inject.Inject // Legacy name: MXDeviceList +@Deprecated("In favor of rust olmMachine") @SessionScope internal class DeviceListManager @Inject constructor(private val cryptoStore: IMXCryptoStore, private val olmDevice: MXOlmDevice, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index 53a3e42d42..8f213ee5d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo @@ -99,7 +100,7 @@ internal class LiveDevice( internal class LiveUserIdentity( internal var userId: String, - private var observer: UserIdentityUpdateObserver, + private var observer: UserIdentityUpdateObserver ) : MutableLiveData>() { override fun onActive() { observer.addUserIdentityUpdateListener(this) @@ -529,8 +530,13 @@ internal class OlmMachine( return when (identity) { is RustUserIdentity.Other -> { - val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel() - val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel() + val verified = this.inner().isIdentityVerified(userId) + val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply { + trustLevel = DeviceTrustLevel(verified, verified) + } + val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel().apply { + trustLevel = DeviceTrustLevel(verified, verified) + } UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachineProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachineProvider.kt new file mode 100644 index 0000000000..f2de3b3724 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachineProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import org.matrix.android.sdk.internal.di.DeviceId +import org.matrix.android.sdk.internal.di.SessionFilesDirectory +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.SessionScope +import java.io.File +import javax.inject.Inject + +@SessionScope +internal class OlmMachineProvider @Inject constructor( + @UserId private val userId: String, + @DeviceId private val deviceId: String?, + @SessionFilesDirectory private val dataDir: File, + requestSender: RequestSender +) { + + private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver() + + var olmMachine: OlmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, requestSender) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt index fefabf2bd4..278c3fec5f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt @@ -37,7 +37,7 @@ internal class QrCodeVerification( private var request: VerificationRequest, private var inner: QrCode?, private val sender: RequestSender, - listeners: ArrayList, + listeners: ArrayList ) : QrCodeVerificationTransaction { private val dispatcher = UpdateDispatcher(listeners) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt new file mode 100644 index 0000000000..d0711567cf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import com.squareup.moshi.Types +import dagger.Lazy +import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.auth.registration.handleUIA +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask +import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse +import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse +import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse +import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo +import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask +import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask +import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask +import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask +import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask +import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.network.parsing.CheckNumberType +import timber.log.Timber +import uniffi.olm.OutgoingVerificationRequest +import uniffi.olm.Request +import uniffi.olm.SignatureUploadRequest +import uniffi.olm.UploadSigningKeysRequest +import javax.inject.Inject + +internal class RequestSender @Inject constructor( + private val sendToDeviceTask: SendToDeviceTask, + private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask, + private val uploadKeysTask: UploadKeysTask, + private val downloadKeysForUsersTask: DownloadKeysForUsersTask, + private val signaturesUploadTask: UploadSignaturesTask, + private val sendVerificationMessageTask: Lazy, + private val uploadSigningKeysTask: UploadSigningKeysTask, + private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask, + private val getKeysBackupVersionTask: GetKeysBackupVersionTask, + private val deleteBackupTask: DeleteBackupTask, + private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, + private val backupRoomKeysTask: StoreSessionsDataTask, + private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, + private val getSessionsDataTask: GetSessionsDataTask, + private val getRoomSessionsDataTask: GetRoomSessionsDataTask, + private val getRoomSessionDataTask: GetRoomSessionDataTask, +) { + companion object { + const val REQUEST_RETRY_COUNT = 3 + } + + suspend fun claimKeys(request: Request.KeysClaim): String { + val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(request.oneTimeKeys) + val response = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, REQUEST_RETRY_COUNT) + val adapter = MoshiProvider + .providesMoshi() + .adapter(KeysClaimResponse::class.java) + return adapter.toJson(response)!! + } + + suspend fun queryKeys(request: Request.KeysQuery): String { + val params = DownloadKeysForUsersTask.Params(request.users, null) + val response = downloadKeysForUsersTask.executeRetry(params, REQUEST_RETRY_COUNT) + val adapter = MoshiProvider.providesMoshi().adapter(KeysQueryResponse::class.java) + return adapter.toJson(response)!! + } + + suspend fun uploadKeys(request: Request.KeysUpload): String { + val body = MoshiProvider.providesMoshi().adapter(Map::class.java).fromJson(request.body)!! + val params = UploadKeysTask.Params(body) + + val response = uploadKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) + val adapter = MoshiProvider.providesMoshi().adapter(KeysUploadResponse::class.java) + + return adapter.toJson(response)!! + } + + suspend fun sendVerificationRequest(request: OutgoingVerificationRequest) { + when (request) { + is OutgoingVerificationRequest.InRoom -> sendRoomMessage(request) + is OutgoingVerificationRequest.ToDevice -> sendToDevice(request) + } + } + + suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom): String { + return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId) + } + + suspend fun sendRoomMessage(request: Request.RoomMessage): String { + return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId) + } + + suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String): String { + val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) + val jsonContent = adapter.fromJson(content) + val event = Event(eventType, transactionId, jsonContent, roomId = roomId) + val params = SendVerificationMessageTask.Params(event) + return this.sendVerificationMessageTask.get().executeRetry(params, REQUEST_RETRY_COUNT) + } + + suspend fun sendSignatureUpload(request: Request.SignatureUpload) { + sendSignatureUpload(request.body) + } + + suspend fun sendSignatureUpload(request: SignatureUploadRequest) { + sendSignatureUpload(request.body) + } + + private suspend fun sendSignatureUpload(body: String) { + val adapter = MoshiProvider.providesMoshi().adapter>>(Map::class.java) + val signatures = adapter.fromJson(body)!! + val params = UploadSignaturesTask.Params(signatures) + this.signaturesUploadTask.executeRetry(params, REQUEST_RETRY_COUNT) + } + + suspend fun uploadCrossSigningKeys( + request: UploadSigningKeysRequest, + interactiveAuthInterceptor: UserInteractiveAuthInterceptor? + ) { + val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java) + val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel() + val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel() + val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel() + + val uploadSigningKeysParams = UploadSigningKeysTask.Params( + masterKey, + userSigningKey, + selfSigningKey, + null + ) + + try { + uploadSigningKeysTask.execute(uploadSigningKeysParams) + } catch (failure: Throwable) { + if (interactiveAuthInterceptor == null + || !handleUIA( + failure = failure, + interceptor = interactiveAuthInterceptor, + retryBlock = { authUpdate -> + uploadSigningKeysTask.executeRetry( + uploadSigningKeysParams.copy(userAuthParam = authUpdate), + REQUEST_RETRY_COUNT + ) + } + ) + ) { + Timber.d("## UIA: propagate failure") + throw failure + } + } + } + + suspend fun sendToDevice(request: Request.ToDevice) { + sendToDevice(request.eventType, request.body, request.requestId) + } + + suspend fun sendToDevice(request: OutgoingVerificationRequest.ToDevice) { + sendToDevice(request.eventType, request.body, request.requestId) + } + + suspend fun sendToDevice(eventType: String, body: String, transactionId: String) { + val adapter = MoshiProvider + .providesMoshi() + .newBuilder() + .add(CheckNumberType.JSON_ADAPTER_FACTORY) + .build() + .adapter>>(Map::class.java) + val jsonBody = adapter.fromJson(body)!! + + val userMap = MXUsersDevicesMap() + userMap.join(jsonBody) + + val sendToDeviceParams = SendToDeviceTask.Params(eventType, userMap, transactionId) + sendToDeviceTask.executeRetry(sendToDeviceParams, REQUEST_RETRY_COUNT) + } + + suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? { + return try { + if (version != null) { + getKeysBackupVersionTask.execute(version) + } else { + getKeysBackupLastVersionTask.execute(Unit) + } + } catch (failure: Throwable) { + if (failure is Failure.ServerError + && failure.error.code == MatrixError.M_NOT_FOUND) { + null + } else { + throw failure + } + } + } + + suspend fun createKeyBackup(body: CreateKeysBackupVersionBody): KeysVersion { + return createKeysBackupVersionTask.execute(body) + } + + suspend fun deleteKeyBackup(version: String) { + val params = DeleteBackupTask.Params(version) + deleteBackupTask.execute(params) + } + + suspend fun backupRoomKeys(request: Request.KeysBackup): String { + val adapter = MoshiProvider + .providesMoshi() + .newBuilder() + .add(CheckNumberType.JSON_ADAPTER_FACTORY) + .build() + .adapter>( + Types.newParameterizedType( + Map::class.java, + String::class.java, + RoomKeysBackupData::class.java + )) + val keys = adapter.fromJson(request.rooms)!! + val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys)) + val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) + val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java) + return responseAdapter.toJson(response)!! + } + + suspend fun updateBackup(keysBackupVersion: KeysVersionResult, body: UpdateKeysBackupVersionBody) { + val params = UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, body) + updateKeysBackupVersionTask.executeRetry(params, REQUEST_RETRY_COUNT) + } + + suspend fun downloadBackedUpKeys(version: String, roomId: String, sessionId: String): KeysBackupData { + val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version)) + + return KeysBackupData(mutableMapOf( + roomId to RoomKeysBackupData(mutableMapOf( + sessionId to data + )) + )) + } + + suspend fun downloadBackedUpKeys(version: String, roomId: String): KeysBackupData { + val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version)) + // Convert to KeysBackupData + return KeysBackupData(mutableMapOf(roomId to data)) + } + + suspend fun downloadBackedUpKeys(version: String): KeysBackupData { + return getSessionsDataTask.execute(GetSessionsDataTask.Params(version)) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt index 42abcd9e89..bf5d0eca9e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt @@ -21,6 +21,8 @@ import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.util.Optional @@ -28,9 +30,18 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.foldToCallback +import javax.inject.Inject -internal class RustCrossSigningService(private val olmMachine: OlmMachine) : CrossSigningService { +internal class RustCrossSigningService @Inject constructor( + @SessionId private val sessionId: String, + @UserId private val myUserId: String, + private val olmMachineProvider: OlmMachineProvider +) : CrossSigningService { + + val olmMachine = olmMachineProvider.olmMachine /** * Is our own device signed by our own cross signing identity */ @@ -89,7 +100,9 @@ internal class RustCrossSigningService(private val olmMachine: OlmMachine) : Cro sskPrivateKey: String? ): UserTrustResult { val export = PrivateKeysInfo(masterKeyPrivateKey, sskPrivateKey, uskKeyPrivateKey) - return runBlocking { olmMachine.importCrossSigningKeys(export) } + return runBlocking { + olmMachineProvider.olmMachine.importCrossSigningKeys(export) + } } /** @@ -206,4 +219,46 @@ internal class RustCrossSigningService(private val olmMachine: OlmMachine) : Cro override fun onSecretUSKGossip(uskPrivateKey: String) { // And this. } + + override suspend fun shieldForGroup(userIds: List): RoomEncryptionTrustLevel { + val myIdentity = olmMachine.getIdentity(myUserId) + val allTrustedUserIds = userIds + .filter { userId -> + olmMachine.getIdentity(userId)?.verified() == true + } + + return if (allTrustedUserIds.isEmpty()) { + RoomEncryptionTrustLevel.Default + } else { + // If one of the verified user as an untrusted device -> warning + // If all devices of all verified users are trusted -> green + // else -> black + allTrustedUserIds + .map { userId -> + olmMachineProvider.olmMachine.getUserDevices(userId) + } + .flatten() + .let { allDevices -> + if (myIdentity != null) { + allDevices.any { !it.toCryptoDeviceInfo().trustLevel?.crossSigningVerified.orFalse() } + } else { + // TODO check that if myIdentity is null ean + // Legacy method + allDevices.any { !it.toCryptoDeviceInfo().isVerified } + } + } + .let { hasWarning -> + if (hasWarning) { + RoomEncryptionTrustLevel.Warning + } else { + if (userIds.size == allTrustedUserIds.size) { + // all users are trusted and all devices are verified + RoomEncryptionTrustLevel.Trusted + } else { + RoomEncryptionTrustLevel.Default + } + } + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt index 53c7fef231..11a00c3b64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt @@ -37,7 +37,7 @@ internal class SasVerification( private val machine: OlmMachine, private var inner: Sas, private val sender: RequestSender, - listeners: ArrayList, + listeners: ArrayList ) : SasVerificationTransaction { private val dispatcher = UpdateDispatcher(listeners) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt index 4d21d796c5..733d4a5e1f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt @@ -250,7 +250,14 @@ internal class UserIdentity( * Convert the identity into a MxCrossSigningInfo class. */ override fun toMxCrossSigningInfo(): MXCrossSigningInfo { - val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey) - return MXCrossSigningInfo(this.userId, crossSigningKeys) +// val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey) + val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false) + // TODO remove this, this is silly, we have way too many methods to check if a user is verified + masterKey.trustLevel = trustLevel + selfSigningKey.trustLevel = trustLevel + return MXCrossSigningInfo(this.userId, listOf( + this.masterKey.also { it.trustLevel = trustLevel }, + this.selfSigningKey.also { it.trustLevel = trustLevel } + )) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt index b98b763355..ebd17307d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt @@ -45,7 +45,7 @@ internal class VerificationRequest( private val machine: OlmMachine, private var inner: VerificationRequest, private val sender: RequestSender, - private val listeners: ArrayList, + private val listeners: ArrayList ) { private val uiHandler = Handler(Looper.getMainLooper()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index d46d58c969..72490322a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt @@ -24,7 +24,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse -import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 83de06a668..64b423cca7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -1,807 +1,807 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.crosssigning - -import androidx.lifecycle.LiveData -import androidx.work.BackoffPolicy -import androidx.work.ExistingWorkPolicy -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask -import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask -import org.matrix.android.sdk.internal.di.SessionId -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.di.WorkManagerProvider -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.TaskThread -import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.logLimit -import org.matrix.android.sdk.internal.worker.WorkerParamsFactory -import org.matrix.olm.OlmPkSigning -import org.matrix.olm.OlmUtility -import timber.log.Timber -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -@SessionScope -internal class DefaultCrossSigningService @Inject constructor( - @UserId private val userId: String, - @SessionId private val sessionId: String, - private val cryptoStore: IMXCryptoStore, - private val deviceListManager: DeviceListManager, - private val initializeCrossSigningTask: InitializeCrossSigningTask, - private val uploadSignaturesTask: UploadSignaturesTask, - private val taskExecutor: TaskExecutor, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope, - private val workManagerProvider: WorkManagerProvider, - private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository -) : CrossSigningService, - DeviceListManager.UserDevicesUpdateListener { - - private var olmUtility: OlmUtility? = null - - private var masterPkSigning: OlmPkSigning? = null - private var userPkSigning: OlmPkSigning? = null - private var selfSigningPkSigning: OlmPkSigning? = null - - init { - try { - olmUtility = OlmUtility() - - // Try to get stored keys if they exist - cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo -> - Timber.i("## CrossSigning - Found Existing self signed keys") - Timber.i("## CrossSigning - Checking if private keys are known") - - cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo -> - privateKeysInfo.master - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning = pkSigning - Timber.i("## CrossSigning - Loading master key success") - } else { - Timber.w("## CrossSigning - Public master key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - privateKeysInfo.user - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading User Signing key success") - } else { - Timber.w("## CrossSigning - Public User key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - privateKeysInfo.selfSigned - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading Self Signing key success") - } else { - Timber.w("## CrossSigning - Public Self Signing key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - } - - // Recover local trust in case private key are there? - setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified()) - } - } catch (e: Throwable) { - // Mmm this kind of a big issue - Timber.e(e, "Failed to initialize Cross Signing") - } - - deviceListManager.addListener(this) - } - - fun release() { - olmUtility?.releaseUtility() - listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() } - deviceListManager.removeListener(this) - } - - protected fun finalize() { - release() - } - - /** - * - Make 3 key pairs (MSK, USK, SSK) - * - Save the private keys with proper security - * - Sign the keys and upload them - * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures - */ - override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback) { - Timber.d("## CrossSigning initializeCrossSigning") - - val params = InitializeCrossSigningTask.Params( - interactiveAuthInterceptor = uiaInterceptor - ) - initializeCrossSigningTask.configureWith(params) { - this.callbackThread = TaskThread.CRYPTO - this.callback = object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.e(failure, "Error in initializeCrossSigning()") - callback.onFailure(failure) - } - - override fun onSuccess(data: InitializeCrossSigningTask.Result) { - val crossSigningInfo = MXCrossSigningInfo(userId, listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo)) - cryptoStore.setMyCrossSigningInfo(crossSigningInfo) - setUserKeysAsTrusted(userId, true) - cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) - masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } - userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } - selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } - - callback.onSuccess(Unit) - } - } - }.executeBy(taskExecutor) - } - - override fun onSecretMSKGossip(mskPrivateKey: String) { - Timber.i("## CrossSigning - onSecretSSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { - Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known") - } - - mskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning?.releaseSigning() - masterPkSigning = pkSigning - Timber.i("## CrossSigning - Loading MSK success") - cryptoStore.storeMSKPrivateKey(mskPrivateKey) - return - } else { - Timber.e("## CrossSigning - onSecretMSKGossip() private key do not match public key") - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - Timber.e("## CrossSigning - onSecretMSKGossip() ${failure.localizedMessage}") - pkSigning.releaseSigning() - } - } - } - - override fun onSecretSSKGossip(sskPrivateKey: String) { - Timber.i("## CrossSigning - onSecretSSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { - Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known") - } - - sskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading SSK success") - cryptoStore.storeSSKPrivateKey(sskPrivateKey) - return - } else { - Timber.e("## CrossSigning - onSecretSSKGossip() private key do not match public key") - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - Timber.e("## CrossSigning - onSecretSSKGossip() ${failure.localizedMessage}") - pkSigning.releaseSigning() - } - } - } - - override fun onSecretUSKGossip(uskPrivateKey: String) { - Timber.i("## CrossSigning - onSecretUSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { - Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ") - } - - uskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning?.releaseSigning() - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading USK success") - cryptoStore.storeUSKPrivateKey(uskPrivateKey) - return - } else { - Timber.e("## CrossSigning - onSecretUSKGossip() private key do not match public key") - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - } - - override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, - uskKeyPrivateKey: String?, - sskPrivateKey: String? - ): UserTrustResult { - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - var masterKeyIsTrusted = false - var userKeyIsTrusted = false - var selfSignedKeyIsTrusted = false - - masterKeyPrivateKey?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning?.releaseSigning() - masterPkSigning = pkSigning - masterKeyIsTrusted = true - Timber.i("## CrossSigning - Loading master key success") - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - - uskKeyPrivateKey?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning?.releaseSigning() - userPkSigning = pkSigning - userKeyIsTrusted = true - Timber.i("## CrossSigning - Loading master key success") - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - - sskPrivateKey?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning - selfSignedKeyIsTrusted = true - Timber.i("## CrossSigning - Loading master key success") - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - - if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) { - return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo) - } else { - cryptoStore.markMyMasterKeyAsLocallyTrusted(true) - val checkSelfTrust = checkSelfTrust() - if (checkSelfTrust.isVerified()) { - cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey) - setUserKeysAsTrusted(userId, true) - } - return checkSelfTrust - } - } - - /** - * - * ┏━━━━━━━━┓ ┏━━━━━━━━┓ - * ┃ ALICE ┃ ┃ BOB ┃ - * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶ MSK - * │ - * │ │ - * │ SSK │ - * │ │ - * │ │ - * └──▶ USK ────────────┘ - */ - override fun isUserTrusted(otherUserId: String): Boolean { - return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true - } - - override fun isCrossSigningVerified(): Boolean { - return checkSelfTrust().isVerified() - } - - /** - * Will not force a download of the key, but will verify signatures trust chain - */ - override fun checkUserTrust(otherUserId: String): UserTrustResult { - Timber.v("## CrossSigning checkUserTrust for $otherUserId") - if (otherUserId == userId) { - return checkSelfTrust() - } - // I trust a user if I trust his master key - // I can trust the master key if it is signed by my user key - // TODO what if the master key is signed by a device key that i have verified - - // First let's get my user key - val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) - - checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) - - return UserTrustResult.Success - } - - fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { - val myUserKey = myCrossSigningInfo?.userKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - if (!myCrossSigningInfo.isTrusted()) { - return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) - } - - // Let's get the other user master key - val otherMasterKey = otherInfo?.masterKey() - ?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") - - val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures - ?.get(userId) // Signatures made by me - ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") - - if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey") - return UserTrustResult.KeyNotSigned(otherMasterKey) - } - - // Check that Alice USK signature of Bob MSK is valid - try { - olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable()) - } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey) - } - - return UserTrustResult.Success - } - - private fun checkSelfTrust(): UserTrustResult { - // Special case when it's me, - // I have to check that MSK -> USK -> SSK - // and that MSK is trusted (i know the private key, or is signed by a trusted device) - val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) - - return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(userId)) - } - - fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List?): UserTrustResult { - // Special case when it's me, - // I have to check that MSK -> USK -> SSK - // and that MSK is trusted (i know the private key, or is signed by a trusted device) +// /* +// * Copyright 2020 The Matrix.org Foundation C.I.C. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +// package org.matrix.android.sdk.internal.crypto.crosssigning +// +// import androidx.lifecycle.LiveData +// import androidx.work.BackoffPolicy +// import androidx.work.ExistingWorkPolicy +// import kotlinx.coroutines.CoroutineScope +// import kotlinx.coroutines.launch +// import org.matrix.android.sdk.api.MatrixCallback +// import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +// import org.matrix.android.sdk.api.extensions.orFalse +// import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +// import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +// import org.matrix.android.sdk.api.util.Optional +// import org.matrix.android.sdk.internal.crypto.DeviceListManager +// import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +// import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder +// import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +// import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo +// import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask +// import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask +// import org.matrix.android.sdk.internal.di.SessionId +// import org.matrix.android.sdk.internal.di.UserId +// import org.matrix.android.sdk.internal.di.WorkManagerProvider +// import org.matrix.android.sdk.internal.session.SessionScope +// import org.matrix.android.sdk.internal.task.TaskExecutor +// import org.matrix.android.sdk.internal.task.TaskThread +// import org.matrix.android.sdk.internal.task.configureWith +// import org.matrix.android.sdk.internal.util.JsonCanonicalizer +// import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +// import org.matrix.android.sdk.internal.util.logLimit +// import org.matrix.android.sdk.internal.worker.WorkerParamsFactory +// import org.matrix.olm.OlmPkSigning +// import org.matrix.olm.OlmUtility +// import timber.log.Timber +// import java.util.concurrent.TimeUnit +// import javax.inject.Inject +// +// @SessionScope +// internal class DefaultCrossSigningService @Inject constructor( +// @UserId private val userId: String, +// @SessionId private val sessionId: String, +// private val cryptoStore: IMXCryptoStore, +// private val deviceListManager: DeviceListManager, +// private val initializeCrossSigningTask: InitializeCrossSigningTask, +// private val uploadSignaturesTask: UploadSignaturesTask, +// private val taskExecutor: TaskExecutor, +// private val coroutineDispatchers: MatrixCoroutineDispatchers, +// private val cryptoCoroutineScope: CoroutineScope, +// private val workManagerProvider: WorkManagerProvider, +// private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository +// ) : CrossSigningService, +// DeviceListManager.UserDevicesUpdateListener { +// +// private var olmUtility: OlmUtility? = null +// +// private var masterPkSigning: OlmPkSigning? = null +// private var userPkSigning: OlmPkSigning? = null +// private var selfSigningPkSigning: OlmPkSigning? = null +// +// init { +// try { +// olmUtility = OlmUtility() +// +// // Try to get stored keys if they exist +// cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo -> +// Timber.i("## CrossSigning - Found Existing self signed keys") +// Timber.i("## CrossSigning - Checking if private keys are known") +// +// cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo -> +// privateKeysInfo.master +// ?.fromBase64() +// ?.let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { +// masterPkSigning = pkSigning +// Timber.i("## CrossSigning - Loading master key success") +// } else { +// Timber.w("## CrossSigning - Public master key does not match the private key") +// pkSigning.releaseSigning() +// // TODO untrust? +// } +// } +// privateKeysInfo.user +// ?.fromBase64() +// ?.let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { +// userPkSigning = pkSigning +// Timber.i("## CrossSigning - Loading User Signing key success") +// } else { +// Timber.w("## CrossSigning - Public User key does not match the private key") +// pkSigning.releaseSigning() +// // TODO untrust? +// } +// } +// privateKeysInfo.selfSigned +// ?.fromBase64() +// ?.let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { +// selfSigningPkSigning = pkSigning +// Timber.i("## CrossSigning - Loading Self Signing key success") +// } else { +// Timber.w("## CrossSigning - Public Self Signing key does not match the private key") +// pkSigning.releaseSigning() +// // TODO untrust? +// } +// } +// } +// +// // Recover local trust in case private key are there? +// setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified()) +// } +// } catch (e: Throwable) { +// // Mmm this kind of a big issue +// Timber.e(e, "Failed to initialize Cross Signing") +// } +// +// deviceListManager.addListener(this) +// } +// +// fun release() { +// olmUtility?.releaseUtility() +// listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() } +// deviceListManager.removeListener(this) +// } +// +// protected fun finalize() { +// release() +// } +// +// /** +// * - Make 3 key pairs (MSK, USK, SSK) +// * - Save the private keys with proper security +// * - Sign the keys and upload them +// * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures +// */ +// override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback) { +// Timber.d("## CrossSigning initializeCrossSigning") +// +// val params = InitializeCrossSigningTask.Params( +// interactiveAuthInterceptor = uiaInterceptor +// ) +// initializeCrossSigningTask.configureWith(params) { +// this.callbackThread = TaskThread.CRYPTO +// this.callback = object : MatrixCallback { +// override fun onFailure(failure: Throwable) { +// Timber.e(failure, "Error in initializeCrossSigning()") +// callback.onFailure(failure) +// } +// +// override fun onSuccess(data: InitializeCrossSigningTask.Result) { +// val crossSigningInfo = MXCrossSigningInfo(userId, listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo)) +// cryptoStore.setMyCrossSigningInfo(crossSigningInfo) +// setUserKeysAsTrusted(userId, true) +// cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) +// masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } +// userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } +// selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } +// +// callback.onSuccess(Unit) +// } +// } +// }.executeBy(taskExecutor) +// } +// +// override fun onSecretMSKGossip(mskPrivateKey: String) { +// Timber.i("## CrossSigning - onSecretSSKGossip") +// val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { +// Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known") +// } +// +// mskPrivateKey.fromBase64() +// .let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// try { +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { +// masterPkSigning?.releaseSigning() +// masterPkSigning = pkSigning +// Timber.i("## CrossSigning - Loading MSK success") +// cryptoStore.storeMSKPrivateKey(mskPrivateKey) +// return +// } else { +// Timber.e("## CrossSigning - onSecretMSKGossip() private key do not match public key") +// pkSigning.releaseSigning() +// } +// } catch (failure: Throwable) { +// Timber.e("## CrossSigning - onSecretMSKGossip() ${failure.localizedMessage}") +// pkSigning.releaseSigning() +// } +// } +// } +// +// override fun onSecretSSKGossip(sskPrivateKey: String) { +// Timber.i("## CrossSigning - onSecretSSKGossip") +// val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { +// Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known") +// } +// +// sskPrivateKey.fromBase64() +// .let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// try { +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { +// selfSigningPkSigning?.releaseSigning() +// selfSigningPkSigning = pkSigning +// Timber.i("## CrossSigning - Loading SSK success") +// cryptoStore.storeSSKPrivateKey(sskPrivateKey) +// return +// } else { +// Timber.e("## CrossSigning - onSecretSSKGossip() private key do not match public key") +// pkSigning.releaseSigning() +// } +// } catch (failure: Throwable) { +// Timber.e("## CrossSigning - onSecretSSKGossip() ${failure.localizedMessage}") +// pkSigning.releaseSigning() +// } +// } +// } +// +// override fun onSecretUSKGossip(uskPrivateKey: String) { +// Timber.i("## CrossSigning - onSecretUSKGossip") +// val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { +// Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ") +// } +// +// uskPrivateKey.fromBase64() +// .let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// try { +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { +// userPkSigning?.releaseSigning() +// userPkSigning = pkSigning +// Timber.i("## CrossSigning - Loading USK success") +// cryptoStore.storeUSKPrivateKey(uskPrivateKey) +// return +// } else { +// Timber.e("## CrossSigning - onSecretUSKGossip() private key do not match public key") +// pkSigning.releaseSigning() +// } +// } catch (failure: Throwable) { +// pkSigning.releaseSigning() +// } +// } +// } +// +// override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, +// uskKeyPrivateKey: String?, +// sskPrivateKey: String? +// ): UserTrustResult { +// val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId) +// +// var masterKeyIsTrusted = false +// var userKeyIsTrusted = false +// var selfSignedKeyIsTrusted = false +// +// masterKeyPrivateKey?.fromBase64() +// ?.let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// try { +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { +// masterPkSigning?.releaseSigning() +// masterPkSigning = pkSigning +// masterKeyIsTrusted = true +// Timber.i("## CrossSigning - Loading master key success") +// } else { +// pkSigning.releaseSigning() +// } +// } catch (failure: Throwable) { +// pkSigning.releaseSigning() +// } +// } +// +// uskKeyPrivateKey?.fromBase64() +// ?.let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// try { +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { +// userPkSigning?.releaseSigning() +// userPkSigning = pkSigning +// userKeyIsTrusted = true +// Timber.i("## CrossSigning - Loading master key success") +// } else { +// pkSigning.releaseSigning() +// } +// } catch (failure: Throwable) { +// pkSigning.releaseSigning() +// } +// } +// +// sskPrivateKey?.fromBase64() +// ?.let { privateKeySeed -> +// val pkSigning = OlmPkSigning() +// try { +// if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { +// selfSigningPkSigning?.releaseSigning() +// selfSigningPkSigning = pkSigning +// selfSignedKeyIsTrusted = true +// Timber.i("## CrossSigning - Loading master key success") +// } else { +// pkSigning.releaseSigning() +// } +// } catch (failure: Throwable) { +// pkSigning.releaseSigning() +// } +// } +// +// if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) { +// return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo) +// } else { +// cryptoStore.markMyMasterKeyAsLocallyTrusted(true) +// val checkSelfTrust = checkSelfTrust() +// if (checkSelfTrust.isVerified()) { +// cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey) +// setUserKeysAsTrusted(userId, true) +// } +// return checkSelfTrust +// } +// } +// +// /** +// * +// * ┏━━━━━━━━┓ ┏━━━━━━━━┓ +// * ┃ ALICE ┃ ┃ BOB ┃ +// * ┗━━━━━━━━┛ ┗━━━━━━━━┛ +// * MSK ┌────────────▶ MSK +// * │ +// * │ │ +// * │ SSK │ +// * │ │ +// * │ │ +// * └──▶ USK ────────────┘ +// */ +// override fun isUserTrusted(otherUserId: String): Boolean { +// return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true +// } +// +// override fun isCrossSigningVerified(): Boolean { +// return checkSelfTrust().isVerified() +// } +// +// /** +// * Will not force a download of the key, but will verify signatures trust chain +// */ +// override fun checkUserTrust(otherUserId: String): UserTrustResult { +// Timber.v("## CrossSigning checkUserTrust for $otherUserId") +// if (otherUserId == userId) { +// return checkSelfTrust() +// } +// // I trust a user if I trust his master key +// // I can trust the master key if it is signed by my user key +// // TODO what if the master key is signed by a device key that i have verified +// +// // First let's get my user key // val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) - - val myMasterKey = myCrossSigningInfo?.masterKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - // Is the master key trusted - // 1) check if I know the private key - val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys() - ?.master - ?.fromBase64() - - var isMaterKeyTrusted = false - if (myMasterKey.trustLevel?.locallyVerified == true) { - isMaterKeyTrusted = true - } else if (masterPrivateKey != null) { - // Check if private match public - var olmPkSigning: OlmPkSigning? = null - try { - olmPkSigning = OlmPkSigning() - val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey) - isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK - } catch (failure: Throwable) { - Timber.e(failure) - } - olmPkSigning?.releaseSigning() - } else { - // Maybe it's signed by a locally trusted device? - myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> - val potentialDeviceId = key.removePrefix("ed25519:") - val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) - if (potentialDevice != null && potentialDevice.isVerified) { - // Check signature validity? - try { - olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable()) - isMaterKeyTrusted = true - return@forEach - } catch (failure: Throwable) { - // log - Timber.w(failure, "Signature not valid?") - } - } - } - } - - if (!isMaterKeyTrusted) { - return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) - } - - val myUserKey = myCrossSigningInfo.userKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures - ?.get(userId) // Signatures made by me - ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") - - if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK") - return UserTrustResult.KeyNotSigned(myUserKey) - } - - // Check that Alice USK signature of Alice MSK is valid - try { - olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable()) - } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey) - } - - val mySSKey = myCrossSigningInfo.selfSigningKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures - ?.get(userId) // Signatures made by me - ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") - - if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK") - return UserTrustResult.KeyNotSigned(mySSKey) - } - - // Check that Alice USK signature of Alice MSK is valid - try { - olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable()) - } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey) - } - - return UserTrustResult.Success - } - - override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { - return cryptoStore.getCrossSigningInfo(otherUserId) - } - - override fun getLiveCrossSigningKeys(userId: String): LiveData> { - return cryptoStore.getLiveCrossSigningInfo(userId) - } - - override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { - return cryptoStore.getMyCrossSigningInfo() - } - - override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { - return cryptoStore.getCrossSigningPrivateKeys() - } - - override fun getLiveCrossSigningPrivateKeys(): LiveData> { - return cryptoStore.getLiveCrossSigningPrivateKeys() - } - - override fun canCrossSign(): Boolean { - return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null && - cryptoStore.getCrossSigningPrivateKeys()?.user != null - } - - override fun allPrivateKeysKnown(): Boolean { - return checkSelfTrust().isVerified() && - cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse() - } - - override fun trustUser(otherUserId: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - Timber.d("## CrossSigning - Mark user $userId as trusted ") - // We should have this user keys - val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() - if (otherMasterKeys == null) { - callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) - return@launch - } - val myKeys = getUserCrossSigningKeys(userId) - if (myKeys == null) { - callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) - return@launch - } - val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey - if (userPubKey == null || userPkSigning == null) { - callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) - return@launch - } - - // Sign the other MasterKey with our UserSigning key - val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, - otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) } - - if (newSignature == null) { - // race?? - callback.onFailure(Throwable("## CrossSigning - Failed to sign")) - return@launch - } - - cryptoStore.setUserKeysAsTrusted(otherUserId, true) - // TODO update local copy with new signature directly here? kind of local echo of trust? - - Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") - val uploadQuery = UploadSignatureQueryBuilder() - .withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) - .build() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - }.executeBy(taskExecutor) - } - } - - override fun markMyMasterKeyAsTrusted() { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - cryptoStore.markMyMasterKeyAsLocallyTrusted(true) - checkSelfTrust() - // re-verify all trusts - onUsersDeviceUpdate(listOf(userId)) - } - } - - override fun trustDevice(deviceId: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - // This device should be yours - val device = cryptoStore.getUserDevice(userId, deviceId) - if (device == null) { - callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) - return@launch - } - - val myKeys = getUserCrossSigningKeys(userId) - if (myKeys == null) { - callback.onFailure(Throwable("CrossSigning is not setup for this account")) - return@launch - } - - val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey - if (ssPubKey == null || selfSigningPkSigning == null) { - callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) - return@launch - } - - // Sign with self signing - val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable()) - - if (newSignature == null) { - // race?? - callback.onFailure(Throwable("Failed to sign")) - return@launch - } - val toUpload = device.copy( - signatures = mapOf( - userId - to - mapOf( - "ed25519:$ssPubKey" to newSignature - ) - ) - ) - - val uploadQuery = UploadSignatureQueryBuilder() - .withDeviceInfo(toUpload) - .build() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - }.executeBy(taskExecutor) - } - } - - override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { - val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) - ?: return DeviceTrustResult.UnknownDevice(otherDeviceId) - - val myKeys = getUserCrossSigningKeys(userId) - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) - - if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) - - val otherKeys = getUserCrossSigningKeys(otherUserId) - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId)) - - // TODO should we force verification ? - if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) - - // Check if the trust chain is valid - /* - * ┏━━━━━━━━┓ ┏━━━━━━━━┓ - * ┃ ALICE ┃ ┃ BOB ┃ - * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶MSK - * │ - * │ │ │ - * │ SSK │ └──▶ SSK ──────────────────┐ - * │ │ │ - * │ │ USK │ - * └──▶ USK ────────────┘ (not visible by │ - * Alice) │ - * ▼ - * ┌──────────────┐ - * │ BOB's Device │ - * └──────────────┘ - */ - - val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") - ?: return legacyFallbackTrust( - locallyTrusted, - DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey() - ?.unpaddedBase64PublicKey - ?: "" - ) - ) - - // Check bob's device is signed by bob's SSK - try { - olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) - } catch (e: Throwable) { - return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e)) - } - - return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) - } - - fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult { - val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified() - myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) - - if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) - - otherKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherDevice.userId)) - - // TODO should we force verification ? - if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) - - // Check if the trust chain is valid - /* - * ┏━━━━━━━━┓ ┏━━━━━━━━┓ - * ┃ ALICE ┃ ┃ BOB ┃ - * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶MSK - * │ - * │ │ │ - * │ SSK │ └──▶ SSK ──────────────────┐ - * │ │ │ - * │ │ USK │ - * └──▶ USK ────────────┘ (not visible by │ - * Alice) │ - * ▼ - * ┌──────────────┐ - * │ BOB's Device │ - * └──────────────┘ - */ - - val otherSSKSignature = otherDevice.signatures?.get(otherKeys.userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") - ?: return legacyFallbackTrust( - locallyTrusted, - DeviceTrustResult.MissingDeviceSignature(otherDevice.deviceId, otherKeys.selfSigningKey() - ?.unpaddedBase64PublicKey - ?: "" - ) - ) - - // Check bob's device is signed by bob's SSK - try { - olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) - } catch (e: Throwable) { - return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e)) - } - - return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) - } - - private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult { - return if (locallyTrusted == true) { - DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true)) - } else { - crossSignTrustFail - } - } - - override fun onUsersDeviceUpdate(userIds: List) { - Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") - val workerParams = UpdateTrustWorker.Params( - sessionId = sessionId, - filename = updateTrustWorkerDataRepository.createParam(userIds) - ) - val workerData = WorkerParamsFactory.toData(workerParams) - - val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() - .setInputData(workerData) - .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) - .build() - - workManagerProvider.workManager - .beginUniqueWork("TRUST_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) - .enqueue() - } - - private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { - val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() - cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) - // If it's me, recheck trust of all users and devices? - val users = ArrayList() - if (otherUserId == userId && currentTrust != trusted) { -// reRequestAllPendingRoomKeyRequest() - cryptoStore.updateUsersTrust { - users.add(it) - checkUserTrust(it).isVerified() - } - - users.forEach { - cryptoStore.getUserDeviceList(it)?.forEach { device -> - val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") - cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) - } - } - } - } - -// private fun reRequestAllPendingRoomKeyRequest() { -// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { -// Timber.d("## CrossSigning - reRequest pending outgoing room key requests") -// cryptoStore.getOutgoingRoomKeyRequests().forEach { -// it.requestBody?.let { requestBody -> -// if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) { -// outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) -// } else { -// outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody) +// +// checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) +// +// return UserTrustResult.Success +// } +// +// fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { +// val myUserKey = myCrossSigningInfo?.userKey() +// ?: return UserTrustResult.CrossSigningNotConfigured(userId) +// +// if (!myCrossSigningInfo.isTrusted()) { +// return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) +// } +// +// // Let's get the other user master key +// val otherMasterKey = otherInfo?.masterKey() +// ?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") +// +// val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures +// ?.get(userId) // Signatures made by me +// ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") +// +// if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { +// Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey") +// return UserTrustResult.KeyNotSigned(otherMasterKey) +// } +// +// // Check that Alice USK signature of Bob MSK is valid +// try { +// olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable()) +// } catch (failure: Throwable) { +// return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey) +// } +// +// return UserTrustResult.Success +// } +// +// private fun checkSelfTrust(): UserTrustResult { +// // Special case when it's me, +// // I have to check that MSK -> USK -> SSK +// // and that MSK is trusted (i know the private key, or is signed by a trusted device) +// val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) +// +// return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(userId)) +// } +// +// fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List?): UserTrustResult { +// // Special case when it's me, +// // I have to check that MSK -> USK -> SSK +// // and that MSK is trusted (i know the private key, or is signed by a trusted device) +// // val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) +// +// val myMasterKey = myCrossSigningInfo?.masterKey() +// ?: return UserTrustResult.CrossSigningNotConfigured(userId) +// +// // Is the master key trusted +// // 1) check if I know the private key +// val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys() +// ?.master +// ?.fromBase64() +// +// var isMaterKeyTrusted = false +// if (myMasterKey.trustLevel?.locallyVerified == true) { +// isMaterKeyTrusted = true +// } else if (masterPrivateKey != null) { +// // Check if private match public +// var olmPkSigning: OlmPkSigning? = null +// try { +// olmPkSigning = OlmPkSigning() +// val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey) +// isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK +// } catch (failure: Throwable) { +// Timber.e(failure) +// } +// olmPkSigning?.releaseSigning() +// } else { +// // Maybe it's signed by a locally trusted device? +// myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> +// val potentialDeviceId = key.removePrefix("ed25519:") +// val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) +// if (potentialDevice != null && potentialDevice.isVerified) { +// // Check signature validity? +// try { +// olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable()) +// isMaterKeyTrusted = true +// return@forEach +// } catch (failure: Throwable) { +// // log +// Timber.w(failure, "Signature not valid?") // } // } // } // } +// +// if (!isMaterKeyTrusted) { +// return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) +// } +// +// val myUserKey = myCrossSigningInfo.userKey() +// ?: return UserTrustResult.CrossSigningNotConfigured(userId) +// +// val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures +// ?.get(userId) // Signatures made by me +// ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") +// +// if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { +// Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK") +// return UserTrustResult.KeyNotSigned(myUserKey) +// } +// +// // Check that Alice USK signature of Alice MSK is valid +// try { +// olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable()) +// } catch (failure: Throwable) { +// return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey) +// } +// +// val mySSKey = myCrossSigningInfo.selfSigningKey() +// ?: return UserTrustResult.CrossSigningNotConfigured(userId) +// +// val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures +// ?.get(userId) // Signatures made by me +// ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") +// +// if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { +// Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK") +// return UserTrustResult.KeyNotSigned(mySSKey) +// } +// +// // Check that Alice USK signature of Alice MSK is valid +// try { +// olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable()) +// } catch (failure: Throwable) { +// return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey) +// } +// +// return UserTrustResult.Success // } -} +// +// override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { +// return cryptoStore.getCrossSigningInfo(otherUserId) +// } +// +// override fun getLiveCrossSigningKeys(userId: String): LiveData> { +// return cryptoStore.getLiveCrossSigningInfo(userId) +// } +// +// override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { +// return cryptoStore.getMyCrossSigningInfo() +// } +// +// override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { +// return cryptoStore.getCrossSigningPrivateKeys() +// } +// +// override fun getLiveCrossSigningPrivateKeys(): LiveData> { +// return cryptoStore.getLiveCrossSigningPrivateKeys() +// } +// +// override fun canCrossSign(): Boolean { +// return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null +// && cryptoStore.getCrossSigningPrivateKeys()?.user != null +// } +// +// override fun allPrivateKeysKnown(): Boolean { +// return checkSelfTrust().isVerified() +// && cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse() +// } +// +// override fun trustUser(otherUserId: String, callback: MatrixCallback) { +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// Timber.d("## CrossSigning - Mark user $userId as trusted ") +// // We should have this user keys +// val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() +// if (otherMasterKeys == null) { +// callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) +// return@launch +// } +// val myKeys = getUserCrossSigningKeys(userId) +// if (myKeys == null) { +// callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) +// return@launch +// } +// val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey +// if (userPubKey == null || userPkSigning == null) { +// callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) +// return@launch +// } +// +// // Sign the other MasterKey with our UserSigning key +// val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, +// otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) } +// +// if (newSignature == null) { +// // race?? +// callback.onFailure(Throwable("## CrossSigning - Failed to sign")) +// return@launch +// } +// +// cryptoStore.setUserKeysAsTrusted(otherUserId, true) +// // TODO update local copy with new signature directly here? kind of local echo of trust? +// +// Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") +// val uploadQuery = UploadSignatureQueryBuilder() +// .withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) +// .build() +// uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { +// this.executionThread = TaskThread.CRYPTO +// this.callback = callback +// }.executeBy(taskExecutor) +// } +// } +// +// override fun markMyMasterKeyAsTrusted() { +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// cryptoStore.markMyMasterKeyAsLocallyTrusted(true) +// checkSelfTrust() +// // re-verify all trusts +// onUsersDeviceUpdate(listOf(userId)) +// } +// } +// +// override fun trustDevice(deviceId: String, callback: MatrixCallback) { +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// // This device should be yours +// val device = cryptoStore.getUserDevice(userId, deviceId) +// if (device == null) { +// callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) +// return@launch +// } +// +// val myKeys = getUserCrossSigningKeys(userId) +// if (myKeys == null) { +// callback.onFailure(Throwable("CrossSigning is not setup for this account")) +// return@launch +// } +// +// val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey +// if (ssPubKey == null || selfSigningPkSigning == null) { +// callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) +// return@launch +// } +// +// // Sign with self signing +// val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable()) +// +// if (newSignature == null) { +// // race?? +// callback.onFailure(Throwable("Failed to sign")) +// return@launch +// } +// val toUpload = device.copy( +// signatures = mapOf( +// userId +// to +// mapOf( +// "ed25519:$ssPubKey" to newSignature +// ) +// ) +// ) +// +// val uploadQuery = UploadSignatureQueryBuilder() +// .withDeviceInfo(toUpload) +// .build() +// uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { +// this.executionThread = TaskThread.CRYPTO +// this.callback = callback +// }.executeBy(taskExecutor) +// } +// } +// +// override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { +// val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) +// ?: return DeviceTrustResult.UnknownDevice(otherDeviceId) +// +// val myKeys = getUserCrossSigningKeys(userId) +// ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) +// +// if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) +// +// val otherKeys = getUserCrossSigningKeys(otherUserId) +// ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId)) +// +// // TODO should we force verification ? +// if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) +// +// // Check if the trust chain is valid +// /* +// * ┏━━━━━━━━┓ ┏━━━━━━━━┓ +// * ┃ ALICE ┃ ┃ BOB ┃ +// * ┗━━━━━━━━┛ ┗━━━━━━━━┛ +// * MSK ┌────────────▶MSK +// * │ +// * │ │ │ +// * │ SSK │ └──▶ SSK ──────────────────┐ +// * │ │ │ +// * │ │ USK │ +// * └──▶ USK ────────────┘ (not visible by │ +// * Alice) │ +// * ▼ +// * ┌──────────────┐ +// * │ BOB's Device │ +// * └──────────────┘ +// */ +// +// val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") +// ?: return legacyFallbackTrust( +// locallyTrusted, +// DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey() +// ?.unpaddedBase64PublicKey +// ?: "" +// ) +// ) +// +// // Check bob's device is signed by bob's SSK +// try { +// olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) +// } catch (e: Throwable) { +// return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e)) +// } +// +// return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) +// } +// +// fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult { +// val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified() +// myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) +// +// if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) +// +// otherKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherDevice.userId)) +// +// // TODO should we force verification ? +// if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) +// +// // Check if the trust chain is valid +// /* +// * ┏━━━━━━━━┓ ┏━━━━━━━━┓ +// * ┃ ALICE ┃ ┃ BOB ┃ +// * ┗━━━━━━━━┛ ┗━━━━━━━━┛ +// * MSK ┌────────────▶MSK +// * │ +// * │ │ │ +// * │ SSK │ └──▶ SSK ──────────────────┐ +// * │ │ │ +// * │ │ USK │ +// * └──▶ USK ────────────┘ (not visible by │ +// * Alice) │ +// * ▼ +// * ┌──────────────┐ +// * │ BOB's Device │ +// * └──────────────┘ +// */ +// +// val otherSSKSignature = otherDevice.signatures?.get(otherKeys.userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") +// ?: return legacyFallbackTrust( +// locallyTrusted, +// DeviceTrustResult.MissingDeviceSignature(otherDevice.deviceId, otherKeys.selfSigningKey() +// ?.unpaddedBase64PublicKey +// ?: "" +// ) +// ) +// +// // Check bob's device is signed by bob's SSK +// try { +// olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) +// } catch (e: Throwable) { +// return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e)) +// } +// +// return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) +// } +// +// private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult { +// return if (locallyTrusted == true) { +// DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true)) +// } else { +// crossSignTrustFail +// } +// } +// +// override fun onUsersDeviceUpdate(userIds: List) { +// Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") +// val workerParams = UpdateTrustWorker.Params( +// sessionId = sessionId, +// filename = updateTrustWorkerDataRepository.createParam(userIds) +// ) +// val workerData = WorkerParamsFactory.toData(workerParams) +// +// val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() +// .setInputData(workerData) +// .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) +// .build() +// +// workManagerProvider.workManager +// .beginUniqueWork("TRUST_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) +// .enqueue() +// } +// +// private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { +// val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() +// cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) +// // If it's me, recheck trust of all users and devices? +// val users = ArrayList() +// if (otherUserId == userId && currentTrust != trusted) { +// // reRequestAllPendingRoomKeyRequest() +// cryptoStore.updateUsersTrust { +// users.add(it) +// checkUserTrust(it).isVerified() +// } +// +// users.forEach { +// cryptoStore.getUserDeviceList(it)?.forEach { device -> +// val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) +// Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") +// cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) +// } +// } +// } +// } +// +// // private fun reRequestAllPendingRoomKeyRequest() { +// // cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// // Timber.d("## CrossSigning - reRequest pending outgoing room key requests") +// // cryptoStore.getOutgoingRoomKeyRequests().forEach { +// // it.requestBody?.let { requestBody -> +// // if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) { +// // outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) +// // } else { +// // outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody) +// // } +// // } +// // } +// // } +// // } +// } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index 3326d3707a..3121e99572 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -1,347 +1,389 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.crosssigning - -import android.content.Context -import androidx.work.WorkerParameters -import com.squareup.moshi.JsonClass -import io.realm.Realm -import io.realm.RealmConfiguration -import io.realm.kotlin.where -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper -import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields -import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper -import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields -import org.matrix.android.sdk.internal.database.awaitTransaction -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.CryptoDatabase -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.SessionComponent -import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import org.matrix.android.sdk.internal.util.logLimit -import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker -import org.matrix.android.sdk.internal.worker.SessionWorkerParams -import timber.log.Timber -import javax.inject.Inject - -internal class UpdateTrustWorker(context: Context, - params: WorkerParameters) : - SessionSafeCoroutineWorker(context, params, Params::class.java) { - - @JsonClass(generateAdapter = true) - internal data class Params( - override val sessionId: String, - override val lastFailureMessage: String? = null, - // Kept for compatibility, but not used anymore (can be used for pending Worker) - val updatedUserIds: List? = null, - // Passing a long list of userId can break the Work Manager due to data size limitation. - // so now we use a temporary file to store the data - val filename: String? = null - ) : SessionWorkerParams - - @Inject lateinit var crossSigningService: DefaultCrossSigningService - - // It breaks the crypto store contract, but we need to batch things :/ - @CryptoDatabase - @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration - - @SessionDatabase - @Inject lateinit var sessionRealmConfiguration: RealmConfiguration - - @UserId - @Inject lateinit var myUserId: String - @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper - @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository - - // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater - @Inject lateinit var cryptoStore: IMXCryptoStore - - override fun injectWith(injector: SessionComponent) { - injector.inject(this) - } - - override suspend fun doSafeWork(params: Params): Result { - val userList = params.filename - ?.let { updateTrustWorkerDataRepository.getParam(it) } - ?.userIds - ?: params.updatedUserIds.orEmpty() - - // List should not be empty, but let's avoid go further in case of empty list - if (userList.isNotEmpty()) { - // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, - // or a new device?) So we check all again :/ - Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}") - updateTrust(userList) - } - - cleanup(params) - return Result.success() - } - - private suspend fun updateTrust(userListParam: List) { - var userList = userListParam - var myCrossSigningInfo: MXCrossSigningInfo? = null - - // First we check that the users MSK are trusted by mine - // After that we check the trust chain for each devices of each users - awaitTransaction(cryptoRealmConfiguration) { cryptoRealm -> - // By mapping here to model, this object is not live - // I should update it if needed - myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) - - var myTrustResult: UserTrustResult? = null - - if (userList.contains(myUserId)) { - Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") - // i am in the list.. but i don't know exactly the delta of change :/ - // If it's my cross signing keys we should refresh all trust - // do it anyway ? - userList = cryptoRealm.where(CrossSigningInfoEntity::class.java) - .findAll() - .mapNotNull { it.userId } - - // check right now my keys and mark it as trusted as other trust depends on it - val myDevices = cryptoRealm.where() - .equalTo(UserEntityFields.USER_ID, myUserId) - .findFirst() - ?.devices - ?.map { CryptoMapper.mapToModel(it) } - - myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices) - updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified()) - // update model reference - myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) - } - - val otherInfos = userList.associateWith { userId -> - getCrossSigningInfo(cryptoRealm, userId) - } - - val trusts = otherInfos.mapValues { entry -> - when (entry.key) { - myUserId -> myTrustResult - else -> { - crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also { - Timber.d("## CrossSigning - user:${entry.key} result:$it") - } - } - } - } - - // TODO! if it's me and my keys has changed... I have to reset trust for everyone! - // i have all the new trusts, update DB - trusts.forEach { - val verified = it.value?.isVerified() == true - updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) - } - - // Ok so now we have to check device trust for all these users.. - Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}") - trusts.keys.forEach { userId -> - val devicesEntities = cryptoRealm.where() - .equalTo(UserEntityFields.USER_ID, userId) - .findFirst() - ?.devices - - val trustMap = devicesEntities?.associateWith { device -> - // get up to date from DB has could have been updated - val otherInfo = getCrossSigningInfo(cryptoRealm, userId) - crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) - } - - // Update trust if needed - devicesEntities?.forEach { device -> - val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() - Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") - if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { - Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") - // need to save - val trustEntity = device.trustLevelEntity - if (trustEntity == null) { - device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { - it.locallyVerified = false - it.crossSignedVerified = crossSignedVerified - } - } else { - trustEntity.crossSignedVerified = crossSignedVerified - } - } - } - } - } - - // So Cross Signing keys trust is updated, device trust is updated - // We can now update room shields? in the session DB? - updateTrustStep2(userList, myCrossSigningInfo) - } - - private suspend fun updateTrustStep2(userList: List, myCrossSigningInfo: MXCrossSigningInfo?) { - Timber.d("## CrossSigning - Updating shields for impacted rooms...") - awaitTransaction(sessionRealmConfiguration) { sessionRealm -> - Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> - sessionRealm.where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) - .distinct(RoomMemberSummaryEntityFields.ROOM_ID) - .findAll() - .map { it.roomId } - .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } - .forEach { roomId -> - RoomSummaryEntity.where(sessionRealm, roomId) - .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) - .findFirst() - ?.let { roomSummary -> - Timber.d("## CrossSigning - Check shield state for room $roomId") - val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() - try { - val updatedTrust = computeRoomShield( - myCrossSigningInfo, - cryptoRealm, - allActiveRoomMembers, - roomSummary - ) - if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { - Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") - roomSummary.roomEncryptionTrustLevel = updatedTrust - } - } catch (failure: Throwable) { - Timber.e(failure) - } - } - } - } - } - } - - private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? { - return cryptoRealm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) - .findFirst() - ?.let { mapCrossSigningInfoEntity(it) } - } - - private fun cleanup(params: Params) { - params.filename - ?.let { updateTrustWorkerDataRepository.delete(it) } - } - - private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) { - cryptoRealm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) - .findFirst() - ?.crossSigningKeys - ?.forEach { info -> - // optimization to avoid trigger updates when there is no change.. - if (info.trustLevelEntity?.isVerified() != verified) { - Timber.d("## CrossSigning - Trust change for $userId : $verified") - val level = info.trustLevelEntity - if (level == null) { - info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { - it.locallyVerified = verified - it.crossSignedVerified = verified - } - } else { - level.locallyVerified = verified - level.crossSignedVerified = verified - } - } - } - } - - private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?, - cryptoRealm: Realm, - activeMemberUserIds: List, - roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { - Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}") - // The set of “all users” depends on the type of room: - // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room - // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room - val listToCheck = if (roomSummaryEntity.isDirect || activeMemberUserIds.size <= 2) { - activeMemberUserIds.filter { it != myUserId } - } else { - activeMemberUserIds - } - - val allTrustedUserIds = listToCheck - .filter { userId -> - getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true - } - - return if (allTrustedUserIds.isEmpty()) { - RoomEncryptionTrustLevel.Default - } else { - // If one of the verified user as an untrusted device -> warning - // If all devices of all verified users are trusted -> green - // else -> black - allTrustedUserIds - .mapNotNull { userId -> - cryptoRealm.where() - .equalTo(UserEntityFields.USER_ID, userId) - .findFirst() - ?.devices - ?.map { CryptoMapper.mapToModel(it) } - } - .flatten() - .let { allDevices -> - Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }.logLimit()}") - if (myCrossSigningInfo != null) { - allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } - } else { - // Legacy method - allDevices.any { !it.isVerified } - } - } - .let { hasWarning -> - if (hasWarning) { - RoomEncryptionTrustLevel.Warning - } else { - if (listToCheck.size == allTrustedUserIds.size) { - // all users are trusted and all devices are verified - RoomEncryptionTrustLevel.Trusted - } else { - RoomEncryptionTrustLevel.Default - } - } - } - } - } - - private fun mapCrossSigningInfoEntity(xsignInfo: CrossSigningInfoEntity): MXCrossSigningInfo { - val userId = xsignInfo.userId ?: "" - return MXCrossSigningInfo( - userId = userId, - crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull { - crossSigningKeysMapper.map(userId, it) - } - ) - } - - override fun buildErrorParams(params: Params, message: String): Params { - return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) - } -} +// /* +// * Copyright 2020 The Matrix.org Foundation C.I.C. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +// package org.matrix.android.sdk.internal.crypto.crosssigning +// +// import android.content.Context +// import androidx.work.WorkerParameters +// import com.squareup.moshi.JsonClass +// import io.realm.Realm +// import io.realm.RealmConfiguration +// import io.realm.kotlin.where +// import kotlinx.coroutines.runBlocking +// import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +// import org.matrix.android.sdk.api.extensions.orFalse +// import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +// import org.matrix.android.sdk.internal.crypto.OlmMachineProvider +// import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +// import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper +// import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity +// import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields +// import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper +// import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity +// import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity +// import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields +// import org.matrix.android.sdk.internal.database.awaitTransaction +// import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +// import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +// import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +// import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +// import org.matrix.android.sdk.internal.database.query.where +// import org.matrix.android.sdk.internal.di.CryptoDatabase +// import org.matrix.android.sdk.internal.di.SessionDatabase +// import org.matrix.android.sdk.internal.di.UserId +// import org.matrix.android.sdk.internal.session.SessionComponent +// import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper +// import org.matrix.android.sdk.internal.util.logLimit +// import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker +// import org.matrix.android.sdk.internal.worker.SessionWorkerParams +// import timber.log.Timber +// import javax.inject.Inject +// +// @Deprecated("Now using olm machine") +// internal class UpdateTrustWorker(context: Context, +// params: WorkerParameters) +// : SessionSafeCoroutineWorker(context, params, Params::class.java) { +// +// @JsonClass(generateAdapter = true) +// internal data class Params( +// override val sessionId: String, +// override val lastFailureMessage: String? = null, +// // Kept for compatibility, but not used anymore (can be used for pending Worker) +// val updatedUserIds: List? = null, +// // Passing a long list of userId can break the Work Manager due to data size limitation. +// // so now we use a temporary file to store the data +// val filename: String? = null +// ) : SessionWorkerParams +// +// // @Inject lateinit var crossSigningService: RustCrossSigningService +// @Inject lateinit var olmMachineProvider: OlmMachineProvider//: RustCrossSigningService +// +// // It breaks the crypto store contract, but we need to batch things :/ +// @CryptoDatabase +// @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration +// +// @SessionDatabase +// @Inject lateinit var sessionRealmConfiguration: RealmConfiguration +// +// @UserId +// @Inject lateinit var myUserId: String +// @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper +// @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository +// +// // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater +// @Inject lateinit var cryptoStore: IMXCryptoStore +// +// override fun injectWith(injector: SessionComponent) { +// injector.inject(this) +// } +// +// override suspend fun doSafeWork(params: Params): Result { +// val userList = params.filename +// ?.let { updateTrustWorkerDataRepository.getParam(it) } +// ?.userIds +// ?: params.updatedUserIds.orEmpty() +// +// // List should not be empty, but let's avoid go further in case of empty list +// if (userList.isNotEmpty()) { +// // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, +// // or a new device?) So we check all again :/ +// Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}") +// updateTrust(userList) +// } +// +// cleanup(params) +// return Result.success() +// } +// +// private suspend fun updateTrust(userListParam: List) { +// var userList = userListParam +// var myCrossSigningInfo: MXCrossSigningInfo? = null +// +// // First we check that the users MSK are trusted by mine +// // After that we check the trust chain for each devices of each users +// awaitTransaction(cryptoRealmConfiguration) { cryptoRealm -> +// // By mapping here to model, this object is not live +// // I should update it if needed +// myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) +// +// var myTrustResult: UserTrustResult? = null +// +// if (userList.contains(myUserId)) { +// Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") +// // i am in the list.. but i don't know exactly the delta of change :/ +// // If it's my cross signing keys we should refresh all trust +// // do it anyway ? +// userList = cryptoRealm.where(CrossSigningInfoEntity::class.java) +// .findAll() +// .mapNotNull { it.userId } +// +// // check right now my keys and mark it as trusted as other trust depends on it +// // val myDevices = olmMachineProvider.olmMachine.getUserDevices(myUserId) +// // cryptoRealm.where() +// // .equalTo(UserEntityFields.USER_ID, myUserId) +// // .findFirst() +// // ?.devices +// // ?.map { CryptoMapper.mapToModel(it) } +// +// +// val identity = olmMachineProvider.olmMachine.getIdentity(olmMachineProvider.olmMachine.userId()) +// myTrustResult = if (identity?.verified() == true) { +// UserTrustResult.Success +// } else { +// if (identity?.toMxCrossSigningInfo() == null) { +// UserTrustResult.CrossSigningNotConfigured(olmMachineProvider.olmMachine.userId()) +// } else { +// UserTrustResult.KeysNotTrusted(identity.toMxCrossSigningInfo()) +// } +// } +// +// // crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices) +// updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified()) +// // update model reference +// myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) +// } +// +// val otherInfos = userList.associateWith { userId -> +// getCrossSigningInfo(cryptoRealm, userId) +// } +// +// val trusts = otherInfos.mapValues { entry -> +// when (entry.key) { +// myUserId -> myTrustResult +// else -> { +// val userId = entry.value?.userId ?: "" +// val identity = olmMachineProvider.olmMachine.getIdentity(userId) +// if (identity?.verified() == true) { +// UserTrustResult.Success +// } else { +// if (identity?.toMxCrossSigningInfo() == null) { +// UserTrustResult.CrossSigningNotConfigured(userId) +// } else { +// UserTrustResult.KeysNotTrusted(identity.toMxCrossSigningInfo()) +// } +// } +// } +// } +// } +// +// // TODO! if it's me and my keys has changed... I have to reset trust for everyone! +// // i have all the new trusts, update DB +// // trusts.forEach { +// // val verified = it.value?.isVerified() == true +// // updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) +// // } +// +// // Ok so now we have to check device trust for all these users.. +// Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}") +// trusts.keys.forEach { userId -> +// val devicesEntities = olmMachineProvider.olmMachine.getUserDevices(userId) +// // cryptoRealm.where() +// // .equalTo(UserEntityFields.USER_ID, userId) +// // .findFirst() +// // ?.devices +// +// val trustMap = devicesEntities.associateWith { device -> +// // val cryptoDevice = device.toCryptoDeviceInfo() +// // get up to date from DB has could have been updated +// // val otherInfo = getCrossSigningInfo(cryptoRealm, userId) +// // val deviceId = device.i deviceId ?: "" +// // val device = olmMachineProvider.olmMachine.getDevice(userId, deviceId) +// // 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() }) +// // } else { +// // DeviceTrustResult.UnknownDevice(deviceId) +// // } +// // crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) +// } +// +// // Update trust if needed +// // devicesEntities?.forEach { device -> +// // val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() +// // Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") +// // if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { +// // Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") +// // // need to save +// // val trustEntity = device.trustLevelEntity +// // if (trustEntity == null) { +// // device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { +// // it.locallyVerified = false +// // it.crossSignedVerified = crossSignedVerified +// // } +// // } else { +// // trustEntity.crossSignedVerified = crossSignedVerified +// // } +// // } +// // } +// } +// } +// +// // So Cross Signing keys trust is updated, device trust is updated +// // We can now update room shields? in the session DB? +// updateTrustStep2(userList, myCrossSigningInfo) +// } +// +// private suspend fun updateTrustStep2(userList: List, myCrossSigningInfo: MXCrossSigningInfo?) { +// Timber.d("## CrossSigning - Updating shields for impacted rooms...") +// awaitTransaction(sessionRealmConfiguration) { sessionRealm -> +// Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> +// sessionRealm.where(RoomMemberSummaryEntity::class.java) +// .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) +// .distinct(RoomMemberSummaryEntityFields.ROOM_ID) +// .findAll() +// .map { it.roomId } +// .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } +// .forEach { roomId -> +// RoomSummaryEntity.where(sessionRealm, roomId) +// .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) +// .findFirst() +// ?.let { roomSummary -> +// Timber.d("## CrossSigning - Check shield state for room $roomId") +// val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() +// try { +// val updatedTrust = computeRoomShield( +// myCrossSigningInfo, +// cryptoRealm, +// allActiveRoomMembers, +// roomSummary +// ) +// if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { +// Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") +// roomSummary.roomEncryptionTrustLevel = updatedTrust +// } +// } catch (failure: Throwable) { +// Timber.e(failure) +// } +// } +// } +// } +// } +// } +// +// private suspend fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? { +// return olmMachineProvider.olmMachine.getIdentity(userId)?.toMxCrossSigningInfo() +// // cryptoRealm.where(CrossSigningInfoEntity::class.java) +// // .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) +// // .findFirst() +// // ?.let { mapCrossSigningInfoEntity(it) } +// } +// +// private fun cleanup(params: Params) { +// params.filename +// ?.let { updateTrustWorkerDataRepository.delete(it) } +// } +// +// private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) { +// cryptoRealm.where(CrossSigningInfoEntity::class.java) +// .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) +// .findFirst() +// ?.crossSigningKeys +// ?.forEach { info -> +// // optimization to avoid trigger updates when there is no change.. +// if (info.trustLevelEntity?.isVerified() != verified) { +// Timber.d("## CrossSigning - Trust change for $userId : $verified") +// val level = info.trustLevelEntity +// if (level == null) { +// info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { +// it.locallyVerified = verified +// it.crossSignedVerified = verified +// } +// } else { +// level.locallyVerified = verified +// level.crossSignedVerified = verified +// } +// } +// } +// } +// +// private suspend fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?, +// cryptoRealm: Realm, +// activeMemberUserIds: List, +// roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { +// Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}") +// // The set of “all users” depends on the type of room: +// // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room +// // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room +// val listToCheck = if (roomSummaryEntity.isDirect || activeMemberUserIds.size <= 2) { +// activeMemberUserIds.filter { it != myUserId } +// } else { +// activeMemberUserIds +// } +// +// val allTrustedUserIds = listToCheck +// .filter { userId -> +// getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true +// } +// Timber.d("## CrossSigning - allTrustedIds ${allTrustedUserIds}") +// +// return if (allTrustedUserIds.isEmpty()) { +// RoomEncryptionTrustLevel.Default +// } else { +// // If one of the verified user as an untrusted device -> warning +// // If all devices of all verified users are trusted -> green +// // else -> black +// allTrustedUserIds +// .mapNotNull { userId -> +// // cryptoRealm.where() +// // .equalTo(UserEntityFields.USER_ID, userId) +// // .findFirst() +// // ?.devices +// // ?.map { CryptoMapper.mapToModel(it) } +// olmMachineProvider.olmMachine.getUserDevices(userId) +// } +// .flatten() +// .let { allDevices -> +// Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { "${it.toCryptoDeviceInfo().deviceId} trust ${it.toCryptoDeviceInfo().trustLevel}" }.logLimit()}") +// +// if (myCrossSigningInfo != null) { +// allDevices.any { !it.toCryptoDeviceInfo().trustLevel?.crossSigningVerified.orFalse() } +// } else { +// // Legacy method +// allDevices.any { !it.toCryptoDeviceInfo().isVerified } +// } +// } +// .let { hasWarning -> +// if (hasWarning) { +// RoomEncryptionTrustLevel.Warning +// } else { +// if (listToCheck.size == allTrustedUserIds.size) { +// // all users are trusted and all devices are verified +// RoomEncryptionTrustLevel.Trusted +// } else { +// RoomEncryptionTrustLevel.Default +// } +// } +// } +// } +// } +// +// private fun mapCrossSigningInfoEntity(xsignInfo: CrossSigningInfoEntity): MXCrossSigningInfo { +// val userId = xsignInfo.userId ?: "" +// return MXCrossSigningInfo( +// userId = userId, +// crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull { +// crossSigningKeysMapper.map(userId, it) +// } +// ) +// } +// +// override fun buildErrorParams(params: Params, message: String): Params { +// return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) +// } +// } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysClaimResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysClaimResponse.kt index 2dfdfe09a8..2309a7254b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysClaimResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysClaimResponse.kt @@ -24,8 +24,8 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) internal data class KeysClaimResponse( - /// If any remote homeservers could not be reached, they are recorded here. - /// The names of the properties are the names of the unreachable servers. + // / If any remote homeservers could not be reached, they are recorded here. + // / The names of the properties are the names of the unreachable servers. @Json(name = "failures") val failures: Map, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt index 356dcd3535..a16cb11eae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt @@ -17,14 +17,11 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.MXKey -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import timber.log.Timber import javax.inject.Inject internal interface ClaimOneTimeKeysForUsersDeviceTask : Task { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt index 7f7e89aa45..8c32466a7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt @@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys -import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt index fd407f5bae..262342b054 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.internal.crypto.verification -import android.os.Handler -import android.os.Looper import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import kotlinx.coroutines.runBlocking @@ -30,7 +28,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.internal.crypto.OlmMachine +import org.matrix.android.sdk.internal.crypto.OlmMachineProvider import org.matrix.android.sdk.internal.crypto.OwnUserIdentity import org.matrix.android.sdk.internal.crypto.SasVerification import org.matrix.android.sdk.internal.crypto.UserIdentity @@ -38,13 +36,15 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE import org.matrix.android.sdk.internal.crypto.model.rest.toValue +import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber +import javax.inject.Inject /** A helper class to deserialize to-device `m.key.verification.*` events to fetch the transaction id out */ @JsonClass(generateAdapter = true) internal data class ToDeviceVerificationEvent( @Json(name = "sender") val sender: String?, - @Json(name = "transaction_id") val transactionId: String, + @Json(name = "transaction_id") val transactionId: String ) /** Helper method to fetch the unique ID of the verification event */ @@ -70,61 +70,13 @@ internal fun prepareMethods(methods: List): List { return stringMethods } -/** Class that implements some common methods to dispatch updates for the verification related classes */ -internal class UpdateDispatcher(private val listeners: ArrayList) { - private val uiHandler = Handler(Looper.getMainLooper()) +@SessionScope +internal class RustVerificationService @Inject constructor(private val olmMachineProvider: OlmMachineProvider) : VerificationService { - internal fun addListener(listener: VerificationService.Listener) { - uiHandler.post { - if (!this.listeners.contains(listener)) { - this.listeners.add(listener) - } - } + val olmMachine by lazy { + olmMachineProvider.olmMachine } - internal fun removeListener(listener: VerificationService.Listener) { - uiHandler.post { this.listeners.remove(listener) } - } - - internal fun dispatchTxAdded(tx: VerificationTransaction) { - uiHandler.post { - this.listeners.forEach { - try { - it.transactionCreated(tx) - } catch (e: Throwable) { - Timber.e(e, "## Error while notifying listeners") - } - } - } - } - - internal fun dispatchTxUpdated(tx: VerificationTransaction) { - uiHandler.post { - this.listeners.forEach { - try { - it.transactionUpdated(tx) - } catch (e: Throwable) { - Timber.e(e, "## Error while notifying listeners") - } - } - } - } - - internal fun dispatchRequestAdded(tx: PendingVerificationRequest) { - Timber.v("## SAS dispatchRequestAdded txId:${tx.transactionId} $tx") - uiHandler.post { - this.listeners.forEach { - try { - it.verificationRequestCreated(tx) - } catch (e: Throwable) { - Timber.e(e, "## Error while notifying listeners") - } - } - } - } -} - -internal class RustVerificationService(private val olmMachine: OlmMachine) : VerificationService { private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners) /** The main entry point for the verification service @@ -279,8 +231,8 @@ internal class RustVerificationService(private val olmMachine: OlmMachine) : Ver ): PendingVerificationRequest { val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) { is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) } - is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices") - null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user") + is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices") + null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user") } return verification.toPendingVerificationRequest() @@ -294,9 +246,9 @@ internal class RustVerificationService(private val olmMachine: OlmMachine) : Ver ): PendingVerificationRequest { Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId") val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) { - is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) } + is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) } is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user") - null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing") + null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing") } return verification.toPendingVerificationRequest() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/UpdateDispatcher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/UpdateDispatcher.kt new file mode 100644 index 0000000000..0b2e1673ec --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/UpdateDispatcher.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.verification + +import android.os.Handler +import android.os.Looper +import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest +import org.matrix.android.sdk.api.session.crypto.verification.VerificationService +import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction +import timber.log.Timber + +/** Class that implements some common methods to dispatch updates for the verification related classes */ +internal class UpdateDispatcher(private val listeners: ArrayList) { + private val uiHandler = Handler(Looper.getMainLooper()) + + internal fun addListener(listener: VerificationService.Listener) { + uiHandler.post { + if (!this.listeners.contains(listener)) { + this.listeners.add(listener) + } + } + } + + internal fun removeListener(listener: VerificationService.Listener) { + uiHandler.post { this.listeners.remove(listener) } + } + + internal fun dispatchTxAdded(tx: VerificationTransaction) { + uiHandler.post { + this.listeners.forEach { + try { + it.transactionCreated(tx) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + } + + internal fun dispatchTxUpdated(tx: VerificationTransaction) { + uiHandler.post { + this.listeners.forEach { + try { + it.transactionUpdated(tx) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + } + + internal fun dispatchRequestAdded(tx: PendingVerificationRequest) { + Timber.v("## SAS dispatchRequestAdded txId:${tx.transactionId} $tx") + uiHandler.post { + this.listeners.forEach { + try { + it.verificationRequestCreated(tx) + } catch (e: Throwable) { + Timber.e(e, "## Error while notifying listeners") + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 3a15e0acf0..a09189b57c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -68,7 +68,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa isEncrypted = roomSummaryEntity.isEncrypted, encryptionEventTs = roomSummaryEntity.encryptionEventTs, breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex, - roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel, + roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel.takeIf { roomSummaryEntity.isEncrypted }, inviterId = roomSummaryEntity.inviterId, hasFailedSending = roomSummaryEntity.hasFailedSending, roomType = roomSummaryEntity.roomType, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index fd4b9fb209..eb325b0e28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -23,9 +23,9 @@ import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker import org.matrix.android.sdk.internal.crypto.CryptoModule +import org.matrix.android.sdk.internal.crypto.OlmMachineProvider import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker import org.matrix.android.sdk.internal.crypto.SendGossipWorker -import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.federation.FederationModule import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker @@ -117,6 +117,8 @@ internal interface SessionComponent { fun taskExecutor(): TaskExecutor + fun olmMachineProvider() : OlmMachineProvider + fun inject(worker: SendEventWorker) fun inject(worker: MultipleEventSendingDispatcherWorker) @@ -137,7 +139,7 @@ internal interface SessionComponent { fun inject(worker: SendGossipWorker) - fun inject(worker: UpdateTrustWorker) +// fun inject(worker: UpdateTrustWorker) @Component.Factory interface Factory { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 84261e6ebf..c9d9dbdbf3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -23,8 +23,8 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.toMedium import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.util.MimeTypes -import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.internal.crypto.OlmMachineProvider import org.matrix.android.sdk.internal.di.AuthenticatedIdentity import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.token.AccessTokenProvider @@ -39,7 +39,8 @@ import javax.inject.Inject internal class CreateRoomBodyBuilder @Inject constructor( private val ensureIdentityTokenTask: EnsureIdentityTokenTask, - private val deviceListManager: DeviceListManager, +// private val deviceListManager: DeviceListManager, + private val olmMachineProvider: OlmMachineProvider, private val identityStore: IdentityStore, private val fileUploader: FileUploader, @UserId @@ -173,14 +174,15 @@ internal class CreateRoomBodyBuilder @Inject constructor( } private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean { - return params.enableEncryptionIfInvitedUsersSupportIt && + return params.enableEncryptionIfInvitedUsersSupportIt // Parity with web, enable if users have encryption ready devices // for now remove checks on cross signing and 3pid invites // && crossSigningService.isCrossSigningVerified() - params.invite3pids.isEmpty() && - params.invitedUserIds.isNotEmpty() && - params.invitedUserIds.let { userIds -> - val keys = deviceListManager.downloadKeys(userIds, forceDownload = false) + && params.invite3pids.isEmpty() + && params.invitedUserIds.isNotEmpty() + && params.invitedUserIds.let { userIds -> + val keys = olmMachineProvider.olmMachine.getUserDevicesMap(userIds) + // deviceListManager.downloadKeys(userIds, forceDownload = false) userIds.all { userId -> keys.map[userId].let { deviceMap -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 3d0f51b831..d9a9da5aff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -17,13 +17,14 @@ package org.matrix.android.sdk.internal.session.room.membership import com.zhuinden.monarchy.Monarchy +import dagger.Lazy import io.realm.Realm import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider -import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity @@ -60,7 +61,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( private val roomSummaryUpdater: RoomSummaryUpdater, private val roomMemberEventHandler: RoomMemberEventHandler, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, - private val deviceListManager: DeviceListManager, + private val cryptoService: Lazy, private val globalErrorReceiver: GlobalErrorReceiver ) : LoadRoomMembersTask { @@ -130,7 +131,10 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { - deviceListManager.onRoomMembersLoadedFor(roomId) + cryptoService.get().onE2ERoomMemberLoadedFromServer(roomId) +// val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true) +// olmMachineProvider.olmMachine.updateTrackedUsers(userIds) +// deviceListManager.onRoomMembersLoadedFor(roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 1d45c8cd5a..4298c20e58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.summary import io.realm.Realm import io.realm.kotlin.createObject +import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes @@ -36,7 +37,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity @@ -71,9 +71,8 @@ internal class RoomSummaryUpdater @Inject constructor( @UserId private val userId: String, private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver, - private val crossSigningService: DefaultCrossSigningService, - private val roomAccountDataDataSource: RoomAccountDataDataSource, - private val normalizer: Normalizer) { + private val crossSigningService: CrossSigningService, + private val roomAccountDataDataSource: RoomAccountDataDataSource) { fun update(realm: Realm, roomId: String, @@ -168,8 +167,8 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) { - // mmm maybe we could only refresh shield instead of checking trust also? - crossSigningService.onUsersDeviceUpdate(otherRoomMembers) + // TODO do we really need to force a shield refresh here? + // crossSigningService.ensureRoomShieldForRoom(roomId) } } } diff --git a/rust-sdk/Makefile b/rust-sdk/Makefile index 131e26bdec..1ceb1dd1c9 100644 --- a/rust-sdk/Makefile +++ b/rust-sdk/Makefile @@ -1,11 +1,19 @@ -x86_64: - cargo build --release --target x86_64-linux-android - install -D target/x86_64-linux-android/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/x86_64/libuniffi_olm.so +# x86_64: +# cargo build --release --target x86_64-linux-android +# mkdir -p ../matrix-sdk-android/src/main/jniLibs/x86_64/ +# cp target/x86_64-linux-android/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/x86_64/libuniffi_olm.so x86: cargo build --release --target i686-linux-android - install -D target/i686-linux-android/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/x86/libuniffi_olm.so + mkdir -p ../matrix-sdk-android/src/main/jniLibs/x86/ + cp target/i686-linux-android/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/x86/libuniffi_olm.so aarch64: cargo build --release --target aarch64-linux-android - install -D target/aarch64-linux-android/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/arm64-v8a/libuniffi_olm.so + mkdir -p ../matrix-sdk-android/src/main/jniLibs/arm64-v8a/ + cp target/aarch64-linux-android/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/arm64-v8a/libuniffi_olm.so + +armv7-linux-androideabi: + cargo build --release --target armv7-linux-androideabi + mkdir -p ../matrix-sdk-android/src/main/jniLibs/armeabi-v7a/ + cp target/armv7-linux-androideabi/release/libmatrix_crypto.so ../matrix-sdk-android/src/main/jniLibs/armeabi-v7a/libuniffi_olm.so \ No newline at end of file