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 b295e484bf..5c5e721100 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 @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.events.model.Content @@ -51,7 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService +import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo @@ -66,6 +67,7 @@ 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 @@ -78,6 +80,8 @@ 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 @@ -98,6 +102,8 @@ 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 @@ -109,7 +115,9 @@ internal class RequestSender @Inject constructor( private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask, private val uploadKeysTask: UploadKeysTask, private val downloadKeysForUsersTask: DownloadKeysForUsersTask, + private val signaturesUploadTask: UploadSignaturesTask, private val sendVerificationMessageTask: Lazy, + private val uploadSigningKeysTask: UploadSigningKeysTask, ) { suspend fun claimKeys(request: Request.KeysClaim): String { @@ -161,6 +169,55 @@ internal class RequestSender @Inject constructor( return this.sendVerificationMessageTask.get().execute(params) } + 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.execute(params) + } + + 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.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate)) + } + ) + ) { + Timber.d("## UIA: propagate failure") + throw failure + } + } + } + suspend fun sendToDevice(request: Request.ToDevice) { sendToDevice(request.eventType, request.body, request.requestId) } @@ -208,7 +265,6 @@ internal class DefaultCryptoService @Inject constructor( private val mxCryptoConfig: MXCryptoConfig, // The key backup service. private val keysBackupService: DefaultKeysBackupService, - private val crossSigningService: DefaultCrossSigningService, // Actions private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository, // Tasks @@ -232,6 +288,9 @@ internal class DefaultCryptoService @Inject constructor( // The verification service. private var verificationService: RustVerificationService? = null + // The cross signing service. + private var crossSigningService: RustCrossSigningService? = null + private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver() // Locks for some of our operations @@ -294,7 +353,9 @@ internal class DefaultCryptoService @Inject constructor( return if (longFormat) "Rust SDK 0.3" else "0.3" } - override fun getMyDevice(): CryptoDeviceInfo = this.olmMachine!!.ownDevice() + override fun getMyDevice(): CryptoDeviceInfo { + return runBlocking { olmMachine!!.ownDevice() } + } override fun fetchDevicesList(callback: MatrixCallback) { getDevicesTask @@ -394,7 +455,8 @@ internal class DefaultCryptoService @Inject constructor( setRustLogger() val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, sender) olmMachine = machine - verificationService = RustVerificationService(machine, this.sender) + verificationService = RustVerificationService(machine) + crossSigningService = RustCrossSigningService(machine) Timber.v( "## CRYPTO | Successfully started up an Olm machine for " + "${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}") @@ -453,7 +515,13 @@ internal class DefaultCryptoService @Inject constructor( return verificationService!! } - override fun crossSigningService() = crossSigningService + override fun crossSigningService(): CrossSigningService { + if (crossSigningService == null) { + internalStart() + } + + return crossSigningService!! + } /** * A sync response has been received @@ -461,6 +529,15 @@ internal class DefaultCryptoService @Inject constructor( suspend fun onSyncCompleted() { if (isStarted()) { sendOutgoingRequests() + // This isn't a copy paste error. Sending the outgoing requests may + // claim one-time keys and establish 1-to-1 Olm sessions with devices, while some + // outgoing requests are waiting for an Olm session to be established (e.g. forwarding + // room keys or sharing secrets). + + // The second call sends out those requests that are waiting for the + // keys claim request to be sent out. + // This could be omitted but then devices might be waiting for the next + sendOutgoingRequests() } cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { @@ -491,7 +568,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getCryptoDeviceInfo(userId: String): List { return runBlocking { - this@DefaultCryptoService.olmMachine?.getUserDevices(userId) ?: listOf() + this@DefaultCryptoService.olmMachine?.getCryptoDeviceInfo(userId) ?: listOf() } } @@ -572,7 +649,7 @@ internal class DefaultCryptoService @Inject constructor( * @return the stored device keys for a user. */ override fun getUserDevices(userId: String): MutableList { - return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList() + return this.getCryptoDeviceInfo(userId).toMutableList() } private fun isEncryptionEnabledForInvitedUser(): Boolean { @@ -845,36 +922,46 @@ internal class DefaultCryptoService @Inject constructor( 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, "{}") + } + private suspend fun sendOutgoingRequests() { outgoingRequestsLock.withLock { coroutineScope { olmMachine!!.outgoingRequests().map { when (it) { - is Request.KeysUpload -> { + is Request.KeysUpload -> { async { uploadKeys(it) } } - is Request.KeysQuery -> { + is Request.KeysQuery -> { async { queryKeys(it) } } - is Request.ToDevice -> { + is Request.ToDevice -> { async { sendToDevice(it) } } - is Request.KeysClaim -> { + is Request.KeysClaim -> { async { claimKeys(it) } } - is Request.RoomMessage -> { + is Request.RoomMessage -> { async { sender.sendRoomMessage(it) } } + is Request.SignatureUpload -> { + async { + signatureUpload(it) + } + } } }.joinAll() } 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 ec89cb5f8a..e83300ac00 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 @@ -20,10 +20,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService +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.rest.UnsignedDeviceInfo import org.matrix.android.sdk.internal.crypto.verification.prepareMethods import uniffi.olm.CryptoStoreErrorException import uniffi.olm.Device as InnerDevice import uniffi.olm.OlmMachine +import uniffi.olm.SignatureErrorException import uniffi.olm.VerificationRequest /** Class representing a device that supports E2EE in the Matrix world @@ -37,10 +41,27 @@ internal class Device( private val sender: RequestSender, private val listeners: ArrayList, ) { - /** Request an interactive verification to begin + @Throws(CryptoStoreErrorException::class) + private suspend fun refreshData() { + val device = withContext(Dispatchers.IO) { + machine.getDevice(inner.userId, inner.deviceId) + } + + if (device != null) { + this.inner = device + } + } + + /** + * Request an interactive verification to begin * * This sends out a m.key.verification.request event over to-device messaging to * to this device. + * + * If no specific device should be verified, but we would like to request + * verification from all our devices, the + * [org.matrix.android.sdk.internal.crypto.OwnUserIdentity.requestVerification] + * method can be used instead. */ @Throws(CryptoStoreErrorException::class) suspend fun requestVerification(methods: List): VerificationRequest? { @@ -61,6 +82,10 @@ internal class Device( * * This sends out a m.key.verification.start event with the method set to * m.sas.v1 to this device using to-device messaging. + * + * This method will soon be deprecated by [MSC3122](https://github.com/matrix-org/matrix-doc/pull/3122). + * The [requestVerification] method should be used instead. + * */ @Throws(CryptoStoreErrorException::class) suspend fun startVerification(): SasVerification? { @@ -78,7 +103,8 @@ internal class Device( } } - /** Mark this device as locally trusted + /** + * Mark this device as locally trusted * * This won't upload any signatures, it will only mark the device as trusted * in the local database. @@ -89,4 +115,56 @@ internal class Device( machine.markDeviceAsTrusted(inner.userId, inner.deviceId) } } + + /** + * Manually verify this device + * + * This will sign the device with our self-signing key and upload the signatures + * to the server. + * + * This will fail if the device doesn't belong to use or if we don't have the + * private part of our self-signing key. + */ + @Throws(SignatureErrorException::class) + suspend fun verify(): Boolean { + val request = withContext(Dispatchers.IO) { + machine.verifyDevice(inner.userId, inner.deviceId) + } + + this.sender.sendSignatureUpload(request) + + return true + } + + /** + * Get the DeviceTrustLevel of this device + */ + @Throws(CryptoStoreErrorException::class) + suspend fun trustLevel(): DeviceTrustLevel { + refreshData() + return DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted) + } + + /** + * Convert this device to a CryptoDeviceInfo. + * + * This will not fetch out fresh data from the Rust side. + **/ + internal fun toCryptoDeviceInfo(): CryptoDeviceInfo { + val keys = this.inner.keys.map { (keyId, key) -> "$keyId:$this.inner.deviceId" to key }.toMap() + + return CryptoDeviceInfo( + this.inner.deviceId, + this.inner.userId, + this.inner.algorithms, + keys, + // The Kotlin side doesn't need to care about signatures, + // so we're not filling this out + mapOf(), + UnsignedDeviceInfo(this.inner.displayName), + DeviceTrustLevel(crossSigningVerified = this.inner.crossSigningTrusted, locallyVerified = this.inner.locallyTrusted), + this.inner.isBlocked, + // TODO + null) + } } 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 21e759cf10..075378a369 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 @@ -24,26 +24,34 @@ import java.util.concurrent.ConcurrentHashMap 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.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction 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.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult 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.MXUsersDevicesMap +import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo +import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse import org.matrix.android.sdk.internal.session.sync.model.DeviceOneTimeKeysCountSyncResponse import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse import timber.log.Timber +import uniffi.olm.CrossSigningKeyExport +import uniffi.olm.CrossSigningStatus import uniffi.olm.CryptoStoreErrorException import uniffi.olm.DecryptionErrorException -import uniffi.olm.Device as InnerDevice import uniffi.olm.DeviceLists import uniffi.olm.KeyRequestPair import uniffi.olm.Logger @@ -51,6 +59,7 @@ import uniffi.olm.OlmMachine as InnerMachine import uniffi.olm.ProgressListener as RustProgressListener import uniffi.olm.Request import uniffi.olm.RequestType +import uniffi.olm.UserIdentity as RustUserIdentity import uniffi.olm.setLogger class CryptoLogger : Logger { @@ -83,27 +92,34 @@ internal class LiveDevice( } } -fun setRustLogger() { - setLogger(CryptoLogger() as Logger) +internal class LiveUserIdentity( + internal var userId: String, + private var observer: UserIdentityUpdateObserver, +) : MutableLiveData>() { + override fun onActive() { + observer.addUserIdentityUpdateListener(this) + } + + override fun onInactive() { + observer.removeUserIdentityUpdateListener(this) + } } -/** Convert a Rust Device into a Kotlin CryptoDeviceInfo */ -private fun toCryptoDeviceInfo(device: InnerDevice): CryptoDeviceInfo { - val keys = device.keys.map { (keyId, key) -> "$keyId:$device.deviceId" to key }.toMap() +internal class LivePrivateCrossSigningKeys( + private var observer: PrivateCrossSigningKeysUpdateObserver, +) : MutableLiveData>() { - return CryptoDeviceInfo( - device.deviceId, - device.userId, - device.algorithms, - keys, - // TODO pass the signatures here, do we need this, why should the - // Kotlin side care about signatures? - mapOf(), - UnsignedDeviceInfo(device.displayName), - DeviceTrustLevel(crossSigningVerified = device.crossSigningTrusted, locallyVerified = device.locallyTrusted), - device.isBlocked, - // TODO - null) + override fun onActive() { + observer.addUserIdentityUpdateListener(this) + } + + override fun onInactive() { + observer.removeUserIdentityUpdateListener(this) + } +} + +fun setRustLogger() { + setLogger(CryptoLogger() as Logger) } internal class DeviceUpdateObserver { @@ -118,6 +134,30 @@ internal class DeviceUpdateObserver { } } +internal class UserIdentityUpdateObserver { + internal val listeners = ConcurrentHashMap() + + fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) { + listeners[userIdentity] = userIdentity.userId + } + + fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) { + listeners.remove(userIdentity) + } +} + +internal class PrivateCrossSigningKeysUpdateObserver { + internal val listeners = ConcurrentHashMap() + + fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) { + listeners[liveKeys] = Unit + } + + fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) { + listeners.remove(liveKeys) + } +} + internal class OlmMachine( user_id: String, device_id: String, @@ -127,6 +167,8 @@ internal class OlmMachine( ) { private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString()) private val deviceUpdateObserver = deviceObserver + private val userIdentityUpdateObserver = UserIdentityUpdateObserver() + private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver() internal val verificationListeners = ArrayList() /** Get our own user ID. */ @@ -148,11 +190,42 @@ internal class OlmMachine( return this.inner } - fun ownDevice(): CryptoDeviceInfo { + /** Update all of our live device listeners. */ + private suspend fun updateLiveDevices() { + for ((liveDevice, users) in deviceUpdateObserver.listeners) { + val devices = getCryptoDeviceInfo(users) + liveDevice.postValue(devices) + } + } + + private suspend fun updateLiveUserIdentities() { + for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) { + val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional() + liveIdentity.postValue(identity) + } + } + + private suspend fun updateLivePrivateKeys() { + val keys = this.exportCrossSigningKeys().toOptional() + + for (liveKeys in privateKeysUpdateObserver.listeners.keys()) { + liveKeys.postValue(keys) + } + } + + /** + * Get our own device info as [CryptoDeviceInfo]. + */ + suspend fun ownDevice(): CryptoDeviceInfo { val deviceId = this.deviceId() val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap() + val crossSigningVerified = when (val ownIdentity = this.getIdentity(this.userId())) { + is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice() + else -> false + } + return CryptoDeviceInfo( this.deviceId(), this.userId(), @@ -161,7 +234,7 @@ internal class OlmMachine( keys, mapOf(), UnsignedDeviceInfo(), - DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), + DeviceTrustLevel(crossSigningVerified, locallyVerified = true), false, null) } @@ -170,7 +243,7 @@ internal class OlmMachine( * Get the list of outgoing requests that need to be sent to the homeserver. * * After the request was sent out and a successful response was received the response body - * should be passed back to the state machine using the markRequestAsSent() method. + * should be passed back to the state machine using the [markRequestAsSent] method. * * @return the list of requests that needs to be sent to the homeserver */ @@ -197,6 +270,7 @@ internal class OlmMachine( if (requestType == RequestType.KEYS_QUERY) { updateLiveDevices() + updateLiveUserIdentities() } } @@ -218,24 +292,30 @@ internal class OlmMachine( toDevice: ToDeviceSyncResponse?, deviceChanges: DeviceListResponse?, keyCounts: DeviceOneTimeKeysCountSyncResponse? - ): ToDeviceSyncResponse = - withContext(Dispatchers.IO) { - val counts: MutableMap = mutableMapOf() + ): ToDeviceSyncResponse { + val response = withContext(Dispatchers.IO) { + val counts: MutableMap = mutableMapOf() - if (keyCounts?.signedCurve25519 != null) { - counts["signed_curve25519"] = keyCounts.signedCurve25519 - } - - val devices = - DeviceLists( - deviceChanges?.changed ?: listOf(), deviceChanges?.left ?: listOf()) - val adapter = - MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java) - val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!! - - adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!! + if (keyCounts?.signedCurve25519 != null) { + counts["signed_curve25519"] = keyCounts.signedCurve25519 } + val devices = + DeviceLists( + deviceChanges?.changed ?: listOf(), deviceChanges?.left ?: listOf()) + val adapter = + MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java) + val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!! + + adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!! + } + + // We may get cross signing keys over a to-device event, update our listeners. + this.updateLivePrivateKeys() + + return response + } + /** * Mark the given list of users to be tracked, triggering a key query request for them. * @@ -251,13 +331,13 @@ internal class OlmMachine( * Generate one-time key claiming requests for all the users we are missing sessions for. * * After the request was sent out and a successful response was received the response body - * should be passed back to the state machine using the markRequestAsSent() method. + * should be passed back to the state machine using the [markRequestAsSent] method. * - * This method should be called every time before a call to shareRoomKey() is made. + * This method should be called every time before a call to [shareRoomKey] is made. * * @param users The list of users for which we would like to establish 1:1 Olm sessions for. * - * @return A keys claim request that needs to be sent out to the server. + * @return A [Request.KeysClaim] request that needs to be sent out to the server. */ @Throws(CryptoStoreErrorException::class) suspend fun getMissingSessions(users: List): Request? = @@ -279,7 +359,7 @@ internal class OlmMachine( * @param users The list of users which are considered to be members of the room and should * receive the room key. * - * @return The list of requests that need to be sent out. + * @return The list of [Request.ToDevice] that need to be sent out. */ @Throws(CryptoStoreErrorException::class) suspend fun shareRoomKey(roomId: String, users: List): List = @@ -288,21 +368,18 @@ internal class OlmMachine( /** * Encrypt the given event with the given type and content for the given room. * - * **Note**: A room key needs to be shared with the group of users that are members in the given - * room. If this is not done this method will panic. + * **Note**: A room key needs to be shared with the group of users that are members + * in the given room. If this is not done this method will panic. * * The usual flow to encrypt an event using this state machine is as follows: * * 1. Get the one-time key claim request to establish 1:1 Olm sessions for - * ``` - * the room members of the room we wish to participate in. This is done - * using the [`get_missing_sessions()`](#method.get_missing_sessions) - * method. This method call should be locked per call. - * ``` - * 2. Share a room key with all the room members using the shareRoomKey(). - * ``` - * This method call should be locked per room. - * ``` + * the room members of the room we wish to participate in. This is done + * using the [getMissingSessions] method. This method call should be locked per call. + * + * 2. Share a room key with all the room members using the [shareRoomKey]. + * This method call should be locked per room. + * * 3. Encrypt the event using this method. * * 4. Send the encrypted event to the server. @@ -316,7 +393,7 @@ internal class OlmMachine( * * @param content the JSON content of the event * - * @return The encrypted version of the content + * @return The encrypted version of the [Content] */ @Throws(CryptoStoreErrorException::class) suspend fun encrypt(roomId: String, eventType: String, content: Content): Content = @@ -334,7 +411,7 @@ internal class OlmMachine( * * @param event The serialized encrypted version of the event. * - * @return the decrypted version of the event. + * @return the decrypted version of the event as a [MXEventDecryptionResult]. */ @Throws(MXCryptoError::class) suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult = @@ -421,9 +498,44 @@ internal class OlmMachine( ImportRoomKeysResult(result.total, result.imported) } + @Throws(CryptoStoreErrorException::class) + suspend fun getIdentity(userId: String): UserIdentities? { + val identity = withContext(Dispatchers.IO) { + inner.getIdentity(userId) + } + val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java) + + return when (identity) { + is RustUserIdentity.Other -> { + val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel() + val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel() + + UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender) + } + is RustUserIdentity.Own -> { + val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel() + val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel() + val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel() + + OwnUserIdentity( + identity.userId, + masterKey, + selfSigningKey, + userSigningKey, + identity.trustsOurOwnDevice, + this, + this.requestSender + ) + } + null -> null + } + } + /** * Get a `Device` from the store. * + * This method returns our own device as well. + * * @param userId The id of the device owner. * * @param deviceId The id of the device itself. @@ -437,21 +549,25 @@ internal class OlmMachine( // using our ownDevice method ownDevice() } else { - val device = getRawDevice(userId, deviceId) ?: return null - toCryptoDeviceInfo(device) + getDevice(userId, deviceId)?.toCryptoDeviceInfo() } } - private suspend fun getRawDevice(userId: String, deviceId: String): InnerDevice? = - withContext(Dispatchers.IO) { - inner.getDevice(userId, deviceId) - } - + @Throws(CryptoStoreErrorException::class) suspend fun getDevice(userId: String, deviceId: String): Device? { - val device = this.getRawDevice(userId, deviceId) ?: return null + val device = withContext(Dispatchers.IO) { + inner.getDevice(userId, deviceId) + } ?: return null + return Device(this.inner, device, this.requestSender, this.verificationListeners) } + suspend fun getUserDevices(userId: String): List { + return withContext(Dispatchers.IO) { + inner.getUserDevices(userId).map { Device(inner, it, requestSender, verificationListeners) } + } + } + /** * Get all devices of an user. * @@ -460,36 +576,18 @@ internal class OlmMachine( * @return The list of Devices or an empty list if there aren't any. */ @Throws(CryptoStoreErrorException::class) - suspend fun getUserDevices(userId: String): List { - val devices = withContext(Dispatchers.IO) { - inner.getUserDevices(userId).map { toCryptoDeviceInfo(it) }.toMutableList() - } + suspend fun getCryptoDeviceInfo(userId: String): List { + val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList() // EA doesn't differentiate much between our own and other devices of // while the rust-sdk does, append our own device here. - val ownDevice = this.ownDevice() - - if (userId == ownDevice.userId) { - devices.add(ownDevice) + if (userId == this.userId()) { + devices.add(this.ownDevice()) } return devices } - suspend fun getUserDevicesMap(userIds: List): MXUsersDevicesMap { - val userMap = MXUsersDevicesMap() - - for (user in userIds) { - val devices = getUserDevices(user) - - for (device in devices) { - userMap.setObject(user, device.deviceId, device) - } - } - - return userMap - } - /** * Get all the devices of multiple users. * @@ -497,34 +595,45 @@ internal class OlmMachine( * * @return The list of Devices or an empty list if there aren't any. */ - suspend fun getUserDevices(userIds: List): List { + private suspend fun getCryptoDeviceInfo(userIds: List): List { val plainDevices: ArrayList = arrayListOf() for (user in userIds) { - val devices = getUserDevices(user) + val devices = this.getCryptoDeviceInfo(user) plainDevices.addAll(devices) } return plainDevices } - /** Mark the device for the given user with the given device ID as trusted - * - * This will mark the device locally as trusted, it won't upload any type of cross - * signing signature - * */ - @Throws(CryptoStoreErrorException::class) - internal suspend fun markDeviceAsTrusted(userId: String, deviceId: String) = - withContext(Dispatchers.IO) { - inner.markDeviceAsTrusted(userId, deviceId) + suspend fun getUserDevicesMap(userIds: List): MXUsersDevicesMap { + val userMap = MXUsersDevicesMap() + + for (user in userIds) { + val devices = this.getCryptoDeviceInfo(user) + + for (device in devices) { + userMap.setObject(user, device.deviceId, device) + } } - /** Update all of our live device listeners. */ - private suspend fun updateLiveDevices() { - for ((liveDevice, users) in deviceUpdateObserver.listeners) { - val devices = getUserDevices(users) - liveDevice.postValue(devices) - } + return userMap + } + + suspend fun getLiveUserIdentity(userId: String): LiveData> { + val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional() + val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver) + liveIdentity.value = identity + + return liveIdentity + } + + suspend fun getLivePrivateCrossSigningKeys(): LiveData> { + val keys = this.exportCrossSigningKeys().toOptional() + val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver) + liveKeys.value = keys + + return liveKeys } /** @@ -538,7 +647,7 @@ internal class OlmMachine( * @return The list of Devices or an empty list if there aren't any. */ suspend fun getLiveDevices(userIds: List): LiveData> { - val plainDevices = getUserDevices(userIds) + val plainDevices = getCryptoDeviceInfo(userIds) val devices = LiveDevice(userIds, deviceUpdateObserver) devices.value = plainDevices @@ -556,7 +665,7 @@ internal class OlmMachine( * @param userId The ID of the user for which we would like to fetch the * verification requests * - * @return The list of VerificationRequests that we share with the given user + * @return The list of [VerificationRequest] that we share with the given user */ fun getVerificationRequests(userId: String): List { return this.inner.getVerificationRequests(userId).map { @@ -585,9 +694,10 @@ internal class OlmMachine( } } - /** Get an active verification for the given user and given flow ID + /** Get an active verification for the given user and given flow ID. * - * This can return a SAS verification or a QR code verification + * @return Either a [SasVerification] verification or a [QrCodeVerification] + * verification. */ fun getVerification(userId: String, flowId: String): VerificationTransaction? { return when (val verification = this.inner.getVerification(userId, flowId)) { @@ -613,4 +723,41 @@ internal class OlmMachine( } } } + + suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) { + val requests = withContext(Dispatchers.IO) { + inner.bootstrapCrossSigning() + } + + this.requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor) + this.requestSender.sendSignatureUpload(requests.signatureRequest) + } + + /** + * Get the status of our private cross signing keys, i.e. which private keys do we have stored locally. + */ + fun crossSigningStatus(): CrossSigningStatus { + return this.inner.crossSigningStatus() + } + + suspend fun exportCrossSigningKeys(): PrivateKeysInfo? { + val export = withContext(Dispatchers.IO) { + inner.exportCrossSigningKeys() + + } ?: return null + + return PrivateKeysInfo(export.masterKey, export.selfSigningKey, export.userSigningKey) + } + + suspend fun importCrossSigningKeys(export: PrivateKeysInfo): UserTrustResult { + val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user) + + withContext(Dispatchers.IO) { + inner.importCrossSigningKeys(rustExport) + } + + this.updateLivePrivateKeys() + // TODO map the errors from importCrossSigningKeys to the UserTrustResult + return UserTrustResult.Success + } } 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 d9d914d789..ea6eba5c6c 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 @@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher import uniffi.olm.CryptoStoreErrorException import uniffi.olm.OlmMachine -import uniffi.olm.OutgoingVerificationRequest import uniffi.olm.QrCode import uniffi.olm.Verification 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 new file mode 100644 index 0000000000..97a15c3af0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt @@ -0,0 +1,209 @@ +/* + * 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 androidx.lifecycle.LiveData +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.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.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.extensions.foldToCallback + +internal class RustCrossSigningService(private val olmMachine: OlmMachine) : CrossSigningService { + /** + * Is our own device signed by our own cross signing identity + */ + override fun isCrossSigningVerified(): Boolean { + return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) { + is OwnUserIdentity -> identity.trustsOurOwnDevice() + else -> false + } + } + + override fun isUserTrusted(otherUserId: String): Boolean { + // This seems to be used only in tests. + return this.checkUserTrust(otherUserId).isVerified() + } + + /** + * Will not force a download of the key, but will verify signatures trust chain. + * Checks that my trusted user key has signed the other user UserKey + */ + override fun checkUserTrust(otherUserId: String): UserTrustResult { + val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) } + + // While UserTrustResult has many different states, they are by the callers + // converted to a boolean value immediately, thus we don't need to support + // all the values. + return if (identity != null) { + val verified = runBlocking { identity.verified() } + + if (verified) { + UserTrustResult.Success + } else { + UserTrustResult.UnknownCrossSignatureInfo(otherUserId) + } + } else { + UserTrustResult.CrossSigningNotConfigured(otherUserId) + } + } + + /** + * Initialize cross signing for this user. + * Users needs to enter credentials + */ + override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback) { + runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) } + } + + /** + * Inject the private cross signing keys, likely from backup, into our store. + * + * This will check if the injected private cross signing keys match the public ones provided + * by the server and if they do so + */ + override fun checkTrustFromPrivateKeys( + masterKeyPrivateKey: String?, + uskKeyPrivateKey: String?, + sskPrivateKey: String? + ): UserTrustResult { + val export = PrivateKeysInfo(masterKeyPrivateKey, sskPrivateKey, uskKeyPrivateKey) + return runBlocking { olmMachine.importCrossSigningKeys(export) } + } + + /** + * Get the public cross signing keys for the given user + * + * @param otherUserId The ID of the user for which we would like to fetch the cross signing keys. + */ + override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { + return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() } + } + + override fun getLiveCrossSigningKeys(userId: String): LiveData> { + return runBlocking { olmMachine.getLiveUserIdentity(userId) } + } + + /** Get our own public cross signing keys */ + override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { + return getUserCrossSigningKeys(olmMachine.userId()) + } + + /** Get our own private cross signing keys */ + override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { + return runBlocking { olmMachine.exportCrossSigningKeys() } + } + + override fun getLiveCrossSigningPrivateKeys(): LiveData> { + return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() } + } + + /** + * Can we sign our other devices or other users? + * + * Returning true means that we have the private self-signing and user-signing keys at hand. + */ + override fun canCrossSign(): Boolean { + val status = this.olmMachine.crossSigningStatus() + + return status.hasSelfSigning && status.hasUserSigning + } + + override fun allPrivateKeysKnown(): Boolean { + val status = this.olmMachine.crossSigningStatus() + + return status.hasMaster && status.hasSelfSigning && status.hasUserSigning + } + + /** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */ + override fun trustUser(otherUserId: String, callback: MatrixCallback) { + // This is only used in a test + val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) } + + if (userIdentity != null) { + runBlocking { userIdentity.verify() } + callback.onSuccess(Unit) + } else { + callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) + } + } + + /** Mark our own master key as trusted */ + override fun markMyMasterKeyAsTrusted() { + // This doesn't seem to be used? + this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback()) + } + + /** + * Sign one of your devices and upload the signature + */ + override fun trustDevice(deviceId: String, callback: MatrixCallback) { + val device = runBlocking { olmMachine.getDevice(olmMachine.userId(), deviceId) } + + return if (device != null) { + val verified = runBlocking { device.verify() } + + if (verified) { + callback.onSuccess(Unit) + } else { + callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) + } + } else { + callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known")) + } + } + + /** + * Check if a device is trusted + * + * This will check that we have a valid trust chain from our own master key to a device, either + * using the self-signing key for our own devices or using the user-signing key and the master + * key of another user. + */ + override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { + val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) } + + return if (device != null) { + // TODO i don't quite understand the semantics here and there are no docs for + // DeviceTrustResult, what do the different result types mean exactly, + // do you return success only if the Device is cross signing verified? + // what about the local trust if it isn't? why is the local trust passed as an argument here? + DeviceTrustResult.Success(runBlocking { device.trustLevel() }) + } else { + DeviceTrustResult.UnknownDevice(otherDeviceId) + } + } + + override fun onSecretMSKGossip(mskPrivateKey: String) { + // This seems to be unused. + } + + override fun onSecretSSKGossip(sskPrivateKey: String) { + // This as well + } + + override fun onSecretUSKGossip(uskPrivateKey: String) { + // And this. + } +} 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 new file mode 100644 index 0000000000..a6d8340063 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt @@ -0,0 +1,256 @@ +/* + * 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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey +import org.matrix.android.sdk.internal.crypto.verification.prepareMethods +import uniffi.olm.CryptoStoreErrorException +import uniffi.olm.SignatureErrorException + +/** + * A sealed class representing user identities. + * + * User identities can come in the form of [OwnUserIdentity] which represents + * our own user identity, or [UserIdentity] which represents a user identity + * belonging to another user. + */ +sealed class UserIdentities { + /** + * The unique ID of the user this identity belongs to. + */ + abstract fun userId(): String + + /** + * Check the verification state of the user identity. + * + * @return True if the identity is considered to be verified and trusted, false otherwise. + */ + @Throws(CryptoStoreErrorException::class) + abstract suspend fun verified(): Boolean + + /** + * Manually verify the user identity. + * + * This will either sign the identity with our user-signing key if + * it is a identity belonging to another user, or sign the identity + * with our own device. + * + * Throws a SignatureErrorException if we can't sign the identity, + * if for example we don't have access to our user-signing key. + */ + @Throws(SignatureErrorException::class) + abstract suspend fun verify() + + /** + * Convert the identity into a MxCrossSigningInfo class. + */ + abstract fun toMxCrossSigningInfo(): MXCrossSigningInfo +} + +/** + * A class representing our own user identity. + * + * This is backed by the public parts of our cross signing keys. + **/ +internal class OwnUserIdentity( + private val userId: String, + private val masterKey: CryptoCrossSigningKey, + private val selfSigningKey: CryptoCrossSigningKey, + private val userSigningKey: CryptoCrossSigningKey, + private val trustsOurOwnDevice: Boolean, + private val olmMachine: OlmMachine, + private val requestSender: RequestSender) : UserIdentities() { + /** + * Our own user id. + */ + override fun userId() = this.userId + + /** + * Manually verify our user identity. + * + * This signs the identity with our own device and upload the signatures to the server. + * + * To perform an interactive verification user the [requestVerification] method instead. + */ + @Throws(SignatureErrorException::class) + override suspend fun verify() { + val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } + this.requestSender.sendSignatureUpload(request) + } + + /** + * Check the verification state of the user identity. + * + * @return True if the identity is considered to be verified and trusted, false otherwise. + */ + @Throws(CryptoStoreErrorException::class) + override suspend fun verified(): Boolean { + return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) } + } + + /** + * Does the identity trust our own device. + */ + fun trustsOurOwnDevice() = this.trustsOurOwnDevice + + /** + * Request an interactive verification to begin + * + * This method should be used if we don't have a specific device we want to verify, + * instead we want to send out a verification request to all our devices. + * + * This sends out an `m.key.verification.request` out to all our devices that support E2EE. + * If the identity should be marked as manually verified, use the [verify] method instead. + * + * If a specific device should be verified instead + * the [org.matrix.android.sdk.internal.crypto.Device.requestVerification] method should be + * used instead. + * + * @param methods The list of [VerificationMethod] that we wish to advertise to the other + * side as being supported. + */ + @Throws(CryptoStoreErrorException::class) + suspend fun requestVerification(methods: List): VerificationRequest { + val stringMethods = prepareMethods(methods) + val result = this.olmMachine.inner().requestSelfVerification(stringMethods) + this.requestSender.sendVerificationRequest(result!!.request) + + return VerificationRequest( + this.olmMachine.inner(), + result.verification, + this.requestSender, + this.olmMachine.verificationListeners + ) + } + + /** + * Convert the identity into a MxCrossSigningInfo class. + */ + override fun toMxCrossSigningInfo(): MXCrossSigningInfo { + val masterKey = this.masterKey + val selfSigningKey = this.selfSigningKey + val userSigningKey = this.userSigningKey + 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 + userSigningKey.trustLevel = trustLevel + + val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey) + return MXCrossSigningInfo(this.userId, crossSigningKeys) + } +} + +/** + * A class representing another users identity. + * + * This is backed by the public parts of the users cross signing keys. + **/ +internal class UserIdentity( + private val userId: String, + private val masterKey: CryptoCrossSigningKey, + private val selfSigningKey: CryptoCrossSigningKey, + private val olmMachine: OlmMachine, + private val requestSender: RequestSender) : UserIdentities() { + /** + * The unique ID of the user that this identity belongs to. + */ + override fun userId() = this.userId + + /** + * Manually verify this user identity. + * + * This signs the identity with our user-signing key. + * + * This method can fail if we don't have the private part of our user-signing key at hand. + * + * To perform an interactive verification user the [requestVerification] method instead. + */ + @Throws(SignatureErrorException::class) + override suspend fun verify() { + val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } + this.requestSender.sendSignatureUpload(request) + } + + /** + * Check the verification state of the user identity. + * + * @return True if the identity is considered to be verified and trusted, false otherwise. + */ + override suspend fun verified(): Boolean { + return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) } + } + + /** + * Request an interactive verification to begin. + * + * This method should be used if we don't have a specific device we want to verify, + * instead we want to send out a verification request to all our devices. For user + * identities that aren't our own, this method should be the primary way to verify users + * and their devices. + * + * This sends out an `m.key.verification.request` out to the room with the given room ID. + * The room **must** be a private DM that we share with this user. + * + * If the identity should be marked as manually verified, use the [verify] method instead. + * + * If a specific device should be verified instead + * the [org.matrix.android.sdk.internal.crypto.Device.requestVerification] method should be + * used instead. + * + * @param methods The list of [VerificationMethod] that we wish to advertise to the other + * side as being supported. + * @param roomId The ID of the room which represents a DM that we share with this user. + * @param transactionId The transaction id that should be used for the request that sends + * the `m.key.verification.request` to the room. + */ + @Throws(CryptoStoreErrorException::class) + suspend fun requestVerification( + methods: List, + roomId: String, + transactionId: String + ): VerificationRequest { + val stringMethods = prepareMethods(methods) + val content = this.olmMachine.inner().verificationRequestContent(this.userId, stringMethods)!! + + val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId) + + val innerRequest = this.olmMachine.inner().requestVerification(this.userId, roomId, eventID, stringMethods)!! + + return VerificationRequest( + this.olmMachine.inner(), + innerRequest, + this.requestSender, + this.olmMachine.verificationListeners + ) + } + + /** + * Convert the identity into a MxCrossSigningInfo class. + */ + override fun toMxCrossSigningInfo(): MXCrossSigningInfo { + val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey) + return MXCrossSigningInfo(this.userId, crossSigningKeys) + } +} 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 417e2d5b0d..fd407f5bae 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 @@ -31,9 +31,9 @@ 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.RequestSender +import org.matrix.android.sdk.internal.crypto.OwnUserIdentity import org.matrix.android.sdk.internal.crypto.SasVerification -import org.matrix.android.sdk.internal.crypto.VerificationRequest +import org.matrix.android.sdk.internal.crypto.UserIdentity import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN 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 @@ -124,10 +124,7 @@ internal class UpdateDispatcher(private val listeners: ArrayList? ): PendingVerificationRequest { - val stringMethods = prepareMethods(methods) - - val result = this.olmMachine.inner().requestSelfVerification(stringMethods) - runBlocking { - requestSender.sendVerificationRequest(result!!.request) + 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") } - return VerificationRequest( - this.olmMachine.inner(), - result!!.verification, - this.requestSender, - this.olmMachine.verificationListeners - ).toPendingVerificationRequest() + return verification.toPendingVerificationRequest() } override fun requestKeyVerificationInDMs( @@ -302,20 +293,13 @@ internal class RustVerificationService( localId: String? ): PendingVerificationRequest { Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId") - val stringMethods = prepareMethods(methods) - val content = this.olmMachine.inner().verificationRequestContent(otherUserId, stringMethods)!! - - val eventID = runBlocking { - requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, localId!!) + val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) { + 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") } - val innerRequest = this.olmMachine.inner().requestVerification(otherUserId, roomId, eventID, stringMethods)!! - return VerificationRequest( - this.olmMachine.inner(), - innerRequest, - this.requestSender, - this.olmMachine.verificationListeners - ).toPendingVerificationRequest() + return verification.toPendingVerificationRequest() } override fun readyPendingVerification(