crypto: Fill out all the methods to support backups

This commit is contained in:
Damir Jelić 2021-10-22 10:44:07 +02:00
parent 021041fc2e
commit 3b93d6b08c
12 changed files with 221 additions and 86 deletions

View File

@ -20,6 +20,7 @@ import android.content.Context
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import com.squareup.moshi.Types
import dagger.Lazy import dagger.Lazy
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -57,17 +58,20 @@ import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.auth.registration.handleUIA 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.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult 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.CreateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData 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.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult 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.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.CreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask 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.GetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask 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.CryptoDeviceInfo 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.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
@ -137,6 +141,7 @@ internal class RequestSender @Inject constructor(
private val deleteBackupTask: DeleteBackupTask, private val deleteBackupTask: DeleteBackupTask,
private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
private val backupRoomKeysTask: StoreSessionsDataTask, private val backupRoomKeysTask: StoreSessionsDataTask,
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
) { ) {
companion object { companion object {
const val REQUEST_RETRY_COUNT = 3 const val REQUEST_RETRY_COUNT = 3
@ -297,44 +302,25 @@ internal class RequestSender @Inject constructor(
val adapter = MoshiProvider val adapter = MoshiProvider
.providesMoshi() .providesMoshi()
.newBuilder() .newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build() .build()
.adapter<MutableMap<String, RoomKeysBackupData>>(MutableMap::class.java) .adapter<MutableMap<String, RoomKeysBackupData>>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
RoomKeysBackupData::class.java
))
val keys = adapter.fromJson(request.rooms)!! val keys = adapter.fromJson(request.rooms)!!
Timber.d("BACKUP: CONVERTED KEYS TO HASHMAP $keys") val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys))
/* val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT)
val keyAdapter = MoshiProvider.providesMoshi().adapter(KeyBackupData::class.java)
val keysBackupData = KeysBackupData()
for (room in keys) {
val sessions = room.value.getOrDefault("sessions", mapOf())
for (session in sessions) {
Timber.d("BACKUP: HEEELOO CONVERTING KEY ${session.value}")
val key = keyAdapter.fromJson(session.value)!!
Timber.d("BACKUP: HEEELOO CONVERTED KEY $key")
keysBackupData
.roomIdToRoomKeysBackupData
.getOrPut(room.key, { RoomKeysBackupData() })
.sessionIdToKeyBackupData[session.key] = key
}
}
*/
/*
for ((roomId, backupData) in keys) {
val roomData = backup.roomIdToRoomKeysBackupData.getOrPut(roomId, { RoomKeysBackupData() })
for ((sessionId, key) in backupData.sessionIdToKeyBackupData) {
Timber.d("BACKUP INSERTING KEY $key")
roomData.sessionIdToKeyBackupData[sessionId] = key
}
}
*/
val params = StoreSessionsDataTask.Params(request.version, KeysBackupData())
val response = backupRoomKeysTask.execute(params)
val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java) val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java)
return responseAdapter.toJson(response)!! return responseAdapter.toJson(response)!!
} }
suspend fun updateBackup(keysBackupVersion: KeysVersionResult, body: UpdateKeysBackupVersionBody) {
val params = UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, body)
updateKeysBackupVersionTask.executeRetry(params, REQUEST_RETRY_COUNT)
}
} }
/** /**
@ -566,6 +552,8 @@ internal class DefaultCryptoService @Inject constructor(
Timber.v("Failed create an Olm machine: $throwable") Timber.v("Failed create an Olm machine: $throwable")
} }
// We try to enable key backups, if the backup version on the server is trusted,
// we're gonna continue backing up.
tryOrNull { tryOrNull {
keysBackupService!!.checkAndStartKeysBackup() keysBackupService!!.checkAndStartKeysBackup()
} }
@ -1075,7 +1063,8 @@ internal class DefaultCryptoService @Inject constructor(
} }
is Request.KeysBackup -> { is Request.KeysBackup -> {
async { async {
TODO() // The rust-sdk won't ever produce KeysBackup requests here,
// those only get explicitly created.
} }
} }
} }

View File

@ -24,10 +24,10 @@ 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.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreErrorException import uniffi.olm.CryptoStoreException
import uniffi.olm.Device as InnerDevice import uniffi.olm.Device as InnerDevice
import uniffi.olm.OlmMachine import uniffi.olm.OlmMachine
import uniffi.olm.SignatureErrorException import uniffi.olm.SignatureException
import uniffi.olm.VerificationRequest import uniffi.olm.VerificationRequest
/** Class representing a device that supports E2EE in the Matrix world /** Class representing a device that supports E2EE in the Matrix world
@ -41,7 +41,7 @@ internal class Device(
private val sender: RequestSender, private val sender: RequestSender,
private val listeners: ArrayList<VerificationService.Listener>, private val listeners: ArrayList<VerificationService.Listener>,
) { ) {
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
private suspend fun refreshData() { private suspend fun refreshData() {
val device = withContext(Dispatchers.IO) { val device = withContext(Dispatchers.IO) {
machine.getDevice(inner.userId, inner.deviceId) machine.getDevice(inner.userId, inner.deviceId)
@ -63,7 +63,7 @@ internal class Device(
* [org.matrix.android.sdk.internal.crypto.OwnUserIdentity.requestVerification] * [org.matrix.android.sdk.internal.crypto.OwnUserIdentity.requestVerification]
* method can be used instead. * method can be used instead.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? { suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
val stringMethods = prepareMethods(methods) val stringMethods = prepareMethods(methods)
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
@ -87,7 +87,7 @@ internal class Device(
* The [requestVerification] method should be used instead. * The [requestVerification] method should be used instead.
* *
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun startVerification(): SasVerification? { suspend fun startVerification(): SasVerification? {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
machine.startSasWithDevice(inner.userId, inner.deviceId) machine.startSasWithDevice(inner.userId, inner.deviceId)
@ -109,7 +109,7 @@ internal class Device(
* This won't upload any signatures, it will only mark the device as trusted * This won't upload any signatures, it will only mark the device as trusted
* in the local database. * in the local database.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun markAsTrusted() { suspend fun markAsTrusted() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
machine.markDeviceAsTrusted(inner.userId, inner.deviceId) machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
@ -125,7 +125,7 @@ internal class Device(
* This will fail if the device doesn't belong to use or if we don't have the * 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. * private part of our self-signing key.
*/ */
@Throws(SignatureErrorException::class) @Throws(SignatureException::class)
suspend fun verify(): Boolean { suspend fun verify(): Boolean {
val request = withContext(Dispatchers.IO) { val request = withContext(Dispatchers.IO) {
machine.verifyDevice(inner.userId, inner.deviceId) machine.verifyDevice(inner.userId, inner.deviceId)
@ -139,7 +139,7 @@ internal class Device(
/** /**
* Get the DeviceTrustLevel of this device * Get the DeviceTrustLevel of this device
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun trustLevel(): DeviceTrustLevel { suspend fun trustLevel(): DeviceTrustLevel {
refreshData() refreshData()
return DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted) return DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted)

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.squareup.moshi.Types
import java.io.File import java.io.File
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -37,6 +38,8 @@ import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional 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.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo 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.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
@ -44,16 +47,18 @@ 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.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse 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.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import timber.log.Timber import timber.log.Timber
import uniffi.olm.BackupKey import uniffi.olm.BackupKey
import uniffi.olm.BackupKeys import uniffi.olm.BackupKeys
import uniffi.olm.CrossSigningKeyExport import uniffi.olm.CrossSigningKeyExport
import uniffi.olm.CrossSigningStatus import uniffi.olm.CrossSigningStatus
import uniffi.olm.CryptoStoreErrorException import uniffi.olm.CryptoStoreException
import uniffi.olm.DecryptionErrorException import uniffi.olm.DecryptionException
import uniffi.olm.DeviceLists import uniffi.olm.DeviceLists
import uniffi.olm.KeyRequestPair import uniffi.olm.KeyRequestPair
import uniffi.olm.Logger import uniffi.olm.Logger
@ -262,7 +267,7 @@ internal class OlmMachine(
* *
* @param responseBody The body of the response that was received. * @param responseBody The body of the response that was received.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun markRequestAsSent( suspend fun markRequestAsSent(
requestId: String, requestId: String,
requestType: RequestType, requestType: RequestType,
@ -290,7 +295,7 @@ internal class OlmMachine(
* *
* @param keyCounts The map of uploaded one-time key types and counts. * @param keyCounts The map of uploaded one-time key types and counts.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun receiveSyncChanges( suspend fun receiveSyncChanges(
toDevice: ToDeviceSyncResponse?, toDevice: ToDeviceSyncResponse?,
deviceChanges: DeviceListResponse?, deviceChanges: DeviceListResponse?,
@ -342,7 +347,7 @@ internal class OlmMachine(
* *
* @return A [Request.KeysClaim] 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) @Throws(CryptoStoreException::class)
suspend fun getMissingSessions(users: List<String>): Request? = suspend fun getMissingSessions(users: List<String>): Request? =
withContext(Dispatchers.IO) { inner.getMissingSessions(users) } withContext(Dispatchers.IO) { inner.getMissingSessions(users) }
@ -364,7 +369,7 @@ internal class OlmMachine(
* *
* @return The list of [Request.ToDevice] that need to be sent out. * @return The list of [Request.ToDevice] that need to be sent out.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> = suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) } withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) }
@ -398,7 +403,7 @@ internal class OlmMachine(
* *
* @return The encrypted version of the [Content] * @return The encrypted version of the [Content]
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content = suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java) val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
@ -453,7 +458,7 @@ internal class OlmMachine(
* request itself. The cancellation *must* be sent out before the request, otherwise devices * request itself. The cancellation *must* be sent out before the request, otherwise devices
* will ignore the key request. * will ignore the key request.
*/ */
@Throws(DecryptionErrorException::class) @Throws(DecryptionException::class)
suspend fun requestRoomKey(event: Event): KeyRequestPair = suspend fun requestRoomKey(event: Event): KeyRequestPair =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
@ -472,7 +477,7 @@ internal class OlmMachine(
* *
* @return the encrypted key export as a bytearray. * @return the encrypted key export as a bytearray.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray = suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray =
withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() } withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() }
@ -485,7 +490,7 @@ internal class OlmMachine(
* *
* @param listener A callback that can be used to introspect the progress of the key import. * @param listener A callback that can be used to introspect the progress of the key import.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun importKeys( suspend fun importKeys(
keys: ByteArray, keys: ByteArray,
passphrase: String, passphrase: String,
@ -501,7 +506,7 @@ internal class OlmMachine(
ImportRoomKeysResult(result.total, result.imported) ImportRoomKeysResult(result.total, result.imported)
} }
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun getIdentity(userId: String): UserIdentities? { suspend fun getIdentity(userId: String): UserIdentities? {
val identity = withContext(Dispatchers.IO) { val identity = withContext(Dispatchers.IO) {
inner.getIdentity(userId) inner.getIdentity(userId)
@ -545,7 +550,7 @@ internal class OlmMachine(
* *
* @return The Device if it found one. * @return The Device if it found one.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? { suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? {
return if (userId == userId() && deviceId == deviceId()) { return if (userId == userId() && deviceId == deviceId()) {
// Our own device isn't part of our store on the Rust side, return it // Our own device isn't part of our store on the Rust side, return it
@ -556,7 +561,7 @@ internal class OlmMachine(
} }
} }
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun getDevice(userId: String, deviceId: String): Device? { suspend fun getDevice(userId: String, deviceId: String): Device? {
val device = withContext(Dispatchers.IO) { val device = withContext(Dispatchers.IO) {
inner.getDevice(userId, deviceId) inner.getDevice(userId, deviceId)
@ -578,7 +583,7 @@ internal class OlmMachine(
* *
* @return The list of Devices or an empty list if there aren't any. * @return The list of Devices or an empty list if there aren't any.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> { suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList() val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
@ -658,7 +663,7 @@ internal class OlmMachine(
} }
/** Discard the currently active room key for the given room if there is one. */ /** Discard the currently active room key for the given room if there is one. */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
fun discardRoomKey(roomId: String) { fun discardRoomKey(roomId: String) {
runBlocking { inner.discardRoomKey(roomId) } runBlocking { inner.discardRoomKey(roomId) }
} }
@ -770,7 +775,7 @@ internal class OlmMachine(
} }
} }
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun enableBackup(key: String, version: String) { suspend fun enableBackup(key: String, version: String) {
return withContext(Dispatchers.Default) { return withContext(Dispatchers.Default) {
val backupKey = BackupKey(key, mapOf(), null) val backupKey = BackupKey(key, mapOf(), null)
@ -797,7 +802,7 @@ internal class OlmMachine(
inner.saveRecoveryKey(key, version) inner.saveRecoveryKey(key, version)
} }
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun backupRoomKeys(): Request? { suspend fun backupRoomKeys(): Request? {
return withContext(Dispatchers.Default) { return withContext(Dispatchers.Default) {
Timber.d("BACKUP CREATING REQUEST") Timber.d("BACKUP CREATING REQUEST")
@ -806,4 +811,18 @@ internal class OlmMachine(
request request
} }
} }
@Throws(CryptoStoreException::class)
suspend fun checkAuthDataSignature(authData: MegolmBackupAuthData): Boolean {
return withContext(Dispatchers.Default) {
val adapter = MoshiProvider
.providesMoshi()
.newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build()
.adapter(MegolmBackupAuthData::class.java)
val serializedAuthData = adapter.toJson(authData)
inner.verifyBackup(serializedAuthData)
}
}
} }

View File

@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
import uniffi.olm.CryptoStoreErrorException import uniffi.olm.CryptoStoreException
import uniffi.olm.OlmMachine import uniffi.olm.OlmMachine
import uniffi.olm.QrCode import uniffi.olm.QrCode
import uniffi.olm.Verification import uniffi.olm.Verification
@ -172,7 +172,7 @@ internal class QrCodeVerification(
* The method turns into a noop if we're not yet ready to confirm the scanning, * The method turns into a noop if we're not yet ready to confirm the scanning,
* i.e. we didn't yet receive a m.key.verification.start event from the other side. * i.e. we didn't yet receive a m.key.verification.start event from the other side.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
private suspend fun confirm() { private suspend fun confirm() {
val result = withContext(Dispatchers.IO) val result = withContext(Dispatchers.IO)
{ {

View File

@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
import uniffi.olm.CryptoStoreErrorException import uniffi.olm.CryptoStoreException
import uniffi.olm.OlmMachine import uniffi.olm.OlmMachine
import uniffi.olm.Sas import uniffi.olm.Sas
import uniffi.olm.Verification import uniffi.olm.Verification
@ -202,7 +202,7 @@ internal class SasVerification(
} }
} }
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
private suspend fun confirm() { private suspend fun confirm() {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
machine.confirmVerification(inner.otherUserId, inner.flowId) machine.confirmVerification(inner.otherUserId, inner.flowId)

View File

@ -25,8 +25,8 @@ 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.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreErrorException import uniffi.olm.CryptoStoreException
import uniffi.olm.SignatureErrorException import uniffi.olm.SignatureException
/** /**
* A sealed class representing user identities. * A sealed class representing user identities.
@ -46,7 +46,7 @@ sealed class UserIdentities {
* *
* @return True if the identity is considered to be verified and trusted, false otherwise. * @return True if the identity is considered to be verified and trusted, false otherwise.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
abstract suspend fun verified(): Boolean abstract suspend fun verified(): Boolean
/** /**
@ -59,7 +59,7 @@ sealed class UserIdentities {
* Throws a SignatureErrorException if we can't sign the identity, * Throws a SignatureErrorException if we can't sign the identity,
* if for example we don't have access to our user-signing key. * if for example we don't have access to our user-signing key.
*/ */
@Throws(SignatureErrorException::class) @Throws(SignatureException::class)
abstract suspend fun verify() abstract suspend fun verify()
/** /**
@ -93,7 +93,7 @@ internal class OwnUserIdentity(
* *
* To perform an interactive verification user the [requestVerification] method instead. * To perform an interactive verification user the [requestVerification] method instead.
*/ */
@Throws(SignatureErrorException::class) @Throws(SignatureException::class)
override suspend fun verify() { override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request) this.requestSender.sendSignatureUpload(request)
@ -104,7 +104,7 @@ internal class OwnUserIdentity(
* *
* @return True if the identity is considered to be verified and trusted, false otherwise. * @return True if the identity is considered to be verified and trusted, false otherwise.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
override suspend fun verified(): Boolean { override suspend fun verified(): Boolean {
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) } return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
} }
@ -130,7 +130,7 @@ internal class OwnUserIdentity(
* @param methods The list of [VerificationMethod] that we wish to advertise to the other * @param methods The list of [VerificationMethod] that we wish to advertise to the other
* side as being supported. * side as being supported.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest { suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
val stringMethods = prepareMethods(methods) val stringMethods = prepareMethods(methods)
val result = this.olmMachine.inner().requestSelfVerification(stringMethods) val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
@ -187,7 +187,7 @@ internal class UserIdentity(
* *
* To perform an interactive verification user the [requestVerification] method instead. * To perform an interactive verification user the [requestVerification] method instead.
*/ */
@Throws(SignatureErrorException::class) @Throws(SignatureException::class)
override suspend fun verify() { override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request) this.requestSender.sendSignatureUpload(request)
@ -225,7 +225,7 @@ internal class UserIdentity(
* @param transactionId The transaction id that should be used for the request that sends * @param transactionId The transaction id that should be used for the request that sends
* the `m.key.verification.request` to the room. * the `m.key.verification.request` to the room.
*/ */
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreException::class)
suspend fun requestVerification( suspend fun requestVerification(
methods: List<VerificationMethod>, methods: List<VerificationMethod>,
roomId: String, roomId: String,

View File

@ -84,6 +84,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.internal.wait
import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData
import org.matrix.olm.OlmException import org.matrix.olm.OlmException
import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkDecryption

View File

@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmB
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion 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.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.extensions.foldToCallback
@ -266,34 +267,148 @@ internal class RustKeyBackupService @Inject constructor(
override fun backupAllGroupSessions(progressListener: ProgressListener?, override fun backupAllGroupSessions(progressListener: ProgressListener?,
callback: MatrixCallback<Unit>?) { callback: MatrixCallback<Unit>?) {
// This is only used in tests?
TODO() TODO()
} }
private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust {
return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isEmpty()) {
Timber.v("getKeysBackupTrust: Key backup is absent or missing required data")
KeysBackupVersionTrust()
} else {
KeysBackupVersionTrust(olmMachine.checkAuthDataSignature(authData))
}
}
override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult,
callback: MatrixCallback<KeysBackupVersionTrust>) { callback: MatrixCallback<KeysBackupVersionTrust>) {
Timber.d("BACKUP: HELLOO TRYING TO CHECK THE TRUST") val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData()
// TODO
callback.onSuccess(KeysBackupVersionTrust(false)) cryptoCoroutineScope.launch {
try {
callback.onSuccess(checkBackupTrust(authData))
} catch (exception: Throwable) {
callback.onFailure(exception)
}
}
} }
override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult,
trust: Boolean, trust: Boolean,
callback: MatrixCallback<Unit>) { callback: MatrixCallback<Unit>) {
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
TODO()
// Get auth data to update it
val authData = getMegolmBackupAuthData(keysBackupVersion)
if (authData == null) {
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
callback.onFailure(IllegalArgumentException("Missing element"))
} else {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val body = withContext(coroutineDispatchers.crypto) {
// Get current signatures, or create an empty set
val userId = olmMachine.userId()
val signatures = authData.signatures[userId].orEmpty().toMutableMap()
if (trust) {
// Add current device signature
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
val deviceSignature = olmMachine.sign(canonicalJson)
deviceSignature[userId]?.forEach { entry ->
signatures[entry.key] = entry.value
}
} else {
signatures.remove("ed25519:${olmMachine.deviceId()}")
}
val newAuthData = authData.copy()
val newSignatures = newAuthData.signatures.toMutableMap()
newSignatures[userId] = signatures
@Suppress("UNCHECKED_CAST")
UpdateKeysBackupVersionBody(
algorithm = keysBackupVersion.algorithm,
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
version = keysBackupVersion.version)
}
try {
sender.updateBackup(keysBackupVersion, body)
val newKeysBackupVersion = KeysVersionResult(
algorithm = keysBackupVersion.algorithm,
authData = body.authData,
version = keysBackupVersion.version,
hash = keysBackupVersion.hash,
count = keysBackupVersion.count
)
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
callback.onSuccess(Unit)
} catch (exception: Throwable) {
callback.onFailure(exception)
}
}
}
}
// Check that the recovery key matches to the public key that we downloaded from the server.
// If they match, we can trust the public key and enable backups since we have the private key.
private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) {
val backupKey = recoveryKey.publicKey()
val authData = getMegolmBackupAuthData(keysBackupData)
when {
authData == null -> {
Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data")
throw IllegalArgumentException("Missing element")
}
backupKey.publicKey != authData.publicKey -> {
Timber.w("isValidRecoveryKeyForKeysBackupVersion: Public keys mismatch")
throw IllegalArgumentException("Invalid recovery key or password")
}
else -> {
// This case is fine, the public key on the server matches the public key the
// recovery key produced.
}
}
} }
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
recoveryKey: String, recoveryKey: String,
callback: MatrixCallback<Unit>) { callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
TODO()
cryptoCoroutineScope.launch {
try {
// This is ~nowhere mentioned, the string here is actually a base58 encoded key.
// This not really supported by the spec for the backup key, the 4S key supports
// base58 encoding and the same method seems to be used here.
val key = BackupRecoveryKey.fromBase58(recoveryKey)
checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true, callback)
} catch (exception: Throwable) {
callback.onFailure(exception)
}
}
} }
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
password: String, password: String,
callback: MatrixCallback<Unit>) { callback: MatrixCallback<Unit>) {
TODO() cryptoCoroutineScope.launch {
try {
val key = BackupRecoveryKey.fromPassphrase(password)
checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true, callback)
} catch (exception: Throwable) {
callback.onFailure(exception)
}
}
} }
override fun onSecretKeyGossip(secret: String) { override fun onSecretKeyGossip(secret: String) {
@ -437,8 +552,9 @@ internal class RustKeyBackupService @Inject constructor(
} }
Timber.v(" -> enabling key backups") Timber.v(" -> enabling key backups")
// TODO cryptoCoroutineScope.launch {
// enableKeysBackup(keyBackupVersion) enableKeysBackup(keyBackupVersion)
}
} else { } else {
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}") Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
if (versionInStore != null) { if (versionInStore != null) {
@ -595,7 +711,7 @@ internal class RustKeyBackupService @Inject constructor(
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response) olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response)
Timber.d("BACKUP MARKED REQUEST AS SENT") Timber.d("BACKUP MARKED REQUEST AS SENT")
// TODO again is this correct? // TODO, again is this correct?
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
backupKeys() backupKeys()
} }

View File

@ -17,7 +17,7 @@ base64 = "0.13.0"
thiserror = "1.0.25" thiserror = "1.0.25"
tracing = "0.1.26" tracing = "0.1.26"
tracing-subscriber = "0.2.18" tracing-subscriber = "0.2.18"
uniffi = "0.12.0" uniffi = "0.14.0"
pbkdf2 = "0.8.0" pbkdf2 = "0.8.0"
sha2 = "0.9.5" sha2 = "0.9.5"
rand = "0.8.4" rand = "0.8.4"
@ -28,14 +28,14 @@ version = "0.2.1"
features = ["lax_deserialize"] features = ["lax_deserialize"]
[dependencies.matrix-sdk-common] [dependencies.matrix-sdk-common]
path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-common/" path = "/home/poljar/werk/matrix/nio-rust/crates/matrix-sdk-common/"
# git = "https://github.com/matrix-org/matrix-rust-sdk/" # git = "https://github.com/matrix-org/matrix-rust-sdk/"
# rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02" # rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02"
[dependencies.matrix-sdk-crypto] [dependencies.matrix-sdk-crypto]
# git = "https://github.com/matrix-org/matrix-rust-sdk/" # git = "https://github.com/matrix-org/matrix-rust-sdk/"
# rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02" # rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02"
path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-crypto/" path = "/home/poljar/werk/matrix/nio-rust/crates/matrix-sdk-crypto/"
features = ["sled_cryptostore", "qrcode", "backups_v1"] features = ["sled_cryptostore", "qrcode", "backups_v1"]
[dependencies.tokio] [dependencies.tokio]

View File

@ -1,11 +1,9 @@
#![deny( #![deny(
dead_code, dead_code,
missing_docs,
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,
unused_extern_crates, unused_extern_crates,
unused_import_braces, unused_import_braces,
unused_qualifications
)] )]
//! TODO //! TODO

View File

@ -1321,4 +1321,12 @@ impl OlmMachine {
}) })
.collect() .collect()
} }
/// TODO
pub fn verify_backup(&self, auth_data: &str) -> Result<bool, CryptoStoreError> {
let auth_data = serde_json::from_str(auth_data)?;
Ok(self
.runtime
.block_on(self.inner.backup_machine().verify_backup(auth_data))?)
}
} }

View File

@ -360,6 +360,8 @@ interface OlmMachine {
[Throws=CryptoStoreError] [Throws=CryptoStoreError]
BackupKeys? get_backup_keys(); BackupKeys? get_backup_keys();
boolean backup_enabled(); boolean backup_enabled();
[Throws=CryptoStoreError]
boolean verify_backup([ByRef] string auth_data);
}; };
dictionary PassphraseInfo { dictionary PassphraseInfo {
@ -389,6 +391,8 @@ interface BackupRecoveryKey {
constructor(string key); constructor(string key);
[Name=from_passphrase] [Name=from_passphrase]
constructor(string key); constructor(string key);
[Name=from_base58]
constructor(string key);
string to_base58(); string to_base58();
BackupKey public_key(); BackupKey public_key();
}; };