crypto: Initial support for server-side backups of room keys

This commit is contained in:
Damir Jelić 2021-10-09 09:48:23 +02:00
parent d3a761a73a
commit 406fd0d8d5
10 changed files with 898 additions and 50 deletions

View file

@ -37,10 +37,12 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.listeners.ProgressListener 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.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener 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.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
@ -54,7 +56,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.util.JsonDict 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.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
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.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.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
@ -119,6 +128,10 @@ internal class RequestSender @Inject constructor(
private val signaturesUploadTask: UploadSignaturesTask, private val signaturesUploadTask: UploadSignaturesTask,
private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>, private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>,
private val uploadSigningKeysTask: UploadSigningKeysTask, private val uploadSigningKeysTask: UploadSigningKeysTask,
private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask,
private val getKeysBackupVersionTask: GetKeysBackupVersionTask,
private val deleteBackupTask: DeleteBackupTask,
private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
) { ) {
companion object { companion object {
const val REQUEST_RETRY_COUNT = 3 const val REQUEST_RETRY_COUNT = 3
@ -192,7 +205,7 @@ internal class RequestSender @Inject constructor(
request: UploadSigningKeysRequest, request: UploadSigningKeysRequest,
interactiveAuthInterceptor: UserInteractiveAuthInterceptor? interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
) { ) {
val adapter = MoshiProvider.providesMoshi().adapter<RestKeyInfo>(RestKeyInfo::class.java) val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel() val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel() val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel() val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
@ -248,6 +261,32 @@ internal class RequestSender @Inject constructor(
val sendToDeviceParams = SendToDeviceTask.Params(eventType, userMap, transactionId) val sendToDeviceParams = SendToDeviceTask.Params(eventType, userMap, transactionId)
sendToDeviceTask.executeRetry(sendToDeviceParams, REQUEST_RETRY_COUNT) sendToDeviceTask.executeRetry(sendToDeviceParams, REQUEST_RETRY_COUNT)
} }
suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? {
return try {
if (version != null) {
getKeysBackupVersionTask.execute(version)
} else {
getKeysBackupLastVersionTask.execute(Unit)
}
} catch (failure: Throwable) {
if (failure is Failure.ServerError
&& failure.error.code == MatrixError.M_NOT_FOUND) {
null
} else {
throw failure
}
}
}
suspend fun createKeyBackup(body: CreateKeysBackupVersionBody): KeysVersion {
return createKeysBackupVersionTask.execute(body)
}
suspend fun deleteKeyBackup(version: String) {
val params = DeleteBackupTask.Params(version)
deleteBackupTask.execute(params)
}
} }
/** /**
@ -272,8 +311,6 @@ internal class DefaultCryptoService @Inject constructor(
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
// Set of parameters used to configure/customize the end-to-end crypto. // Set of parameters used to configure/customize the end-to-end crypto.
private val mxCryptoConfig: MXCryptoConfig, private val mxCryptoConfig: MXCryptoConfig,
// The key backup service.
private val keysBackupService: DefaultKeysBackupService,
// Actions // Actions
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository, private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
// Tasks // Tasks
@ -283,6 +320,7 @@ internal class DefaultCryptoService @Inject constructor(
private val setDeviceNameTask: SetDeviceNameTask, private val setDeviceNameTask: SetDeviceNameTask,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope, private val cryptoCoroutineScope: CoroutineScope,
@ -300,6 +338,9 @@ internal class DefaultCryptoService @Inject constructor(
// The cross signing service. // The cross signing service.
private var crossSigningService: RustCrossSigningService? = null private var crossSigningService: RustCrossSigningService? = null
// The key backup service.
private var keysBackupService: RustKeyBackupService? = null
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver() private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
// Locks for some of our operations // Locks for some of our operations
@ -448,9 +489,12 @@ internal class DefaultCryptoService @Inject constructor(
cryptoStore.open() cryptoStore.open()
// this can throw if no backup // this can throw if no backup
/*
TODO
tryOrNull { tryOrNull {
keysBackupService.checkAndStartKeysBackup() keysBackupService.checkAndStartKeysBackup()
} }
*/
} }
} }
@ -466,6 +510,7 @@ internal class DefaultCryptoService @Inject constructor(
olmMachine = machine olmMachine = machine
verificationService = RustVerificationService(machine) verificationService = RustVerificationService(machine)
crossSigningService = RustCrossSigningService(machine) crossSigningService = RustCrossSigningService(machine)
keysBackupService = RustKeyBackupService(machine, sender, coroutineDispatchers, cryptoCoroutineScope)
Timber.v( Timber.v(
"## CRYPTO | Successfully started up an Olm machine for " + "## CRYPTO | Successfully started up an Olm machine for " +
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}") "${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
@ -473,6 +518,10 @@ internal class DefaultCryptoService @Inject constructor(
Timber.v("Failed create an Olm machine: $throwable") Timber.v("Failed create an Olm machine: $throwable")
} }
tryOrNull {
keysBackupService!!.checkAndStartKeysBackup()
}
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
@ -494,7 +543,12 @@ internal class DefaultCryptoService @Inject constructor(
/** /**
* @return the Keys backup Service * @return the Keys backup Service
*/ */
override fun keysBackupService() = keysBackupService override fun keysBackupService(): KeysBackupService {
if (keysBackupService == null) {
internalStart()
}
return keysBackupService!!
}
/** /**
* @return the VerificationService * @return the VerificationService
@ -693,7 +747,7 @@ internal class DefaultCryptoService @Inject constructor(
eventType: String, eventType: String,
roomId: String, roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) { callback: MatrixCallback<MXEncryptEventContentResult>) {
// moved to crypto scope to have uptodate values // moved to crypto scope to have up to date values
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
@ -971,6 +1025,11 @@ internal class DefaultCryptoService @Inject constructor(
signatureUpload(it) signatureUpload(it)
} }
} }
is Request.KeysBackup -> {
async {
TODO()
}
}
} }
}.joinAll() }.joinAll()
} }

View file

@ -48,6 +48,8 @@ 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 timber.log.Timber import timber.log.Timber
import uniffi.olm.BackupKey
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.CryptoStoreErrorException
@ -59,6 +61,7 @@ import uniffi.olm.OlmMachine as InnerMachine
import uniffi.olm.ProgressListener as RustProgressListener import uniffi.olm.ProgressListener as RustProgressListener
import uniffi.olm.Request import uniffi.olm.Request
import uniffi.olm.RequestType import uniffi.olm.RequestType
import uniffi.olm.RoomKeyCounts
import uniffi.olm.UserIdentity as RustUserIdentity import uniffi.olm.UserIdentity as RustUserIdentity
import uniffi.olm.setLogger import uniffi.olm.setLogger
@ -760,4 +763,33 @@ internal class OlmMachine(
// TODO map the errors from importCrossSigningKeys to the UserTrustResult // TODO map the errors from importCrossSigningKeys to the UserTrustResult
return UserTrustResult.Success return UserTrustResult.Success
} }
suspend fun sign(message: String): Map<String, Map<String, String>> {
return withContext(Dispatchers.Default) {
inner.sign(message)
}
}
@Throws(CryptoStoreErrorException::class)
suspend fun enableBackup(key: String, version: String) {
return withContext(Dispatchers.Default) {
val backupKey = BackupKey(key, mapOf(), null)
inner.enableBackup(backupKey, version)
}
}
fun roomKeyCounts(): RoomKeyCounts {
// TODO convert this to a suspendable method
return inner.roomKeyCounts()
}
fun getBackupKeys(): BackupKeys? {
// TODO this needs to be suspendable
return inner.getBackupKeys()
}
fun saveRecoveryKey(key: String?, version: String?) {
// TODO convert this to a suspendable method
inner.saveRecoveryKey(key, version)
}
} }

View file

@ -0,0 +1,466 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.keysbackup
import android.os.Handler
import android.os.Looper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.RequestSender
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData
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.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import timber.log.Timber
import uniffi.olm.BackupRecoveryKey
import javax.inject.Inject
/**
* A DefaultKeysBackupService class instance manage incremental backup of e2e keys (megolm keys)
* to the user's homeserver.
*/
@SessionScope
internal class RustKeyBackupService @Inject constructor(
private val olmMachine: OlmMachine,
private val sender: RequestSender,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope,
) : KeysBackupService {
private val uiHandler = Handler(Looper.getMainLooper())
private val keysBackupStateManager = KeysBackupStateManager(uiHandler)
// The backup version
override var keysBackupVersion: KeysVersionResult? = null
private set
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
private var keysBackupStateListener: KeysBackupStateListener? = null
override val isEnabled: Boolean
get() = keysBackupStateManager.isEnabled
override val isStucked: Boolean
get() = keysBackupStateManager.isStucked
override val state: KeysBackupState
get() = keysBackupStateManager.state
override val currentBackupVersion: String?
get() = keysBackupVersion?.version
override fun addListener(listener: KeysBackupStateListener) {
keysBackupStateManager.addListener(listener)
}
override fun removeListener(listener: KeysBackupStateListener) {
keysBackupStateManager.removeListener(listener)
}
override fun prepareKeysBackupVersion(password: String?,
progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
val key = if (password != null) {
BackupRecoveryKey.fromPassphrase(password)
} else {
BackupRecoveryKey()
}
val publicKey = key.publicKey()
val backupAuthData = SignalableMegolmBackupAuthData(
publicKey = publicKey.publicKey,
privateKeySalt = publicKey.passphraseInfo?.privateKeySalt,
privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations
)
val canonicalJson = JsonCanonicalizer.getCanonicalJson(
Map::class.java,
backupAuthData.signalableJSONDictionary()
)
val signedMegolmBackupAuthData = MegolmBackupAuthData(
publicKey = backupAuthData.publicKey,
privateKeySalt = backupAuthData.privateKeySalt,
privateKeyIterations = backupAuthData.privateKeyIterations,
signatures = olmMachine.sign(canonicalJson)
)
MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = signedMegolmBackupAuthData,
recoveryKey = key.toBase58()
)
}
}.foldToCallback(callback)
}
}
override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
callback: MatrixCallback<KeysVersion>) {
@Suppress("UNCHECKED_CAST")
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
algorithm = keysBackupCreationInfo.algorithm,
authData = keysBackupCreationInfo.authData.toJsonDict()
)
keysBackupStateManager.state = KeysBackupState.Enabling
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
try {
val data = sender.createKeyBackup(createKeysBackupVersionBody)
// Reset backup markers.
// Don't we need to join the task here? Isn't this a race condition?
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// TODO reset our backup state here, i.e. the `backed_up` flag on inbound group sessions
}
olmMachine.enableBackup(keysBackupCreationInfo.authData.publicKey, data.version)
callback.onSuccess(data)
} catch (failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure)
}
}
}
override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) {
cryptoCoroutineScope.launch {
olmMachine.saveRecoveryKey(recoveryKey, version)
}
}
private fun resetBackupAllGroupSessionsListeners() {
backupAllGroupSessionsCallback = null
keysBackupStateListener?.let {
keysBackupStateManager.removeListener(it)
}
keysBackupStateListener = null
}
/**
* Reset all local key backup data.
*
* Note: This method does not update the state
*/
private fun resetKeysBackupData() {
resetBackupAllGroupSessionsListeners()
/*
TODO reset data on the rust side
cryptoStore.setKeyBackupVersion(null)
cryptoStore.setKeysBackupData(null)
backupOlmPkEncryption?.releaseEncryption()
backupOlmPkEncryption = null
// Reset backup markers
cryptoStore.resetBackupMarkers()
*/
}
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.crypto) {
if (keysBackupVersion != null && version == keysBackupVersion?.version) {
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Unknown
}
fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
}
}
try {
sender.deleteKeyBackup(version)
eventuallyRestartBackup()
uiHandler.post { callback?.onSuccess(Unit) }
} catch (failure: Throwable) {
eventuallyRestartBackup()
uiHandler.post { callback?.onFailure(failure) }
}
}
}
}
override fun canRestoreKeys(): Boolean {
// TODO
return false
}
override fun getTotalNumbersOfKeys(): Int {
return olmMachine.roomKeyCounts().total.toInt()
}
override fun getTotalNumbersOfBackedUpKeys(): Int {
return olmMachine.roomKeyCounts().backedUp.toInt()
}
override fun backupAllGroupSessions(progressListener: ProgressListener?,
callback: MatrixCallback<Unit>?) {
TODO()
}
override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult,
callback: MatrixCallback<KeysBackupVersionTrust>) {
Timber.d("BACKUP: HELLOO TRYING TO CHECK THE TRUST")
// TODO
callback.onSuccess(KeysBackupVersionTrust(false))
}
override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult,
trust: Boolean,
callback: MatrixCallback<Unit>) {
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
TODO()
}
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
recoveryKey: String,
callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
TODO()
}
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
password: String,
callback: MatrixCallback<Unit>) {
TODO()
}
override fun onSecretKeyGossip(secret: String) {
Timber.i("## CrossSigning - onSecretKeyGossip")
TODO()
}
override fun getBackupProgress(progressListener: ProgressListener) {
val backedUpKeys = getTotalNumbersOfBackedUpKeys()
val total = getTotalNumbersOfKeys()
progressListener.onProgress(backedUpKeys, total)
}
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: String,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>) {
// TODO
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
}
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
password: String,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>) {
// TODO
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
}
override fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
sender.getKeyBackupVersion(version)
}.foldToCallback(callback)
}
}
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
sender.getKeyBackupVersion()
}.foldToCallback(callback)
}
}
private suspend fun forceUsingLastVersionHelper(): Boolean {
val response = sender.getKeyBackupVersion()
val serverBackupVersion = response?.version
val localBackupVersion = keysBackupVersion?.version
Timber.d("BACKUP: $serverBackupVersion")
return if (serverBackupVersion == null) {
if (localBackupVersion == null) {
// No backup on the server, and backup is not active
true
} else {
// No backup on the server, and we are currently backing up, so stop backing up
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Disabled
false
}
} else {
if (localBackupVersion == null) {
// Do a check
checkAndStartWithKeysBackupVersion(response)
// backup on the server, and backup is not active
false
} else {
// Backup on the server, and we are currently backing up, compare version
if (localBackupVersion == serverBackupVersion) {
// We are already using the last version of the backup
true
} else {
// This will automatically check for the last version then
deleteBackup(localBackupVersion, null)
// We are not using the last version, so delete the current version we are using on the server
false
}
}
}
}
override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) {
cryptoCoroutineScope.launch {
runCatching {
forceUsingLastVersionHelper()
}.foldToCallback(callback)
}
}
override fun checkAndStartKeysBackup() {
if (!isStucked) {
// Try to start or restart the backup only if it is in unknown or bad state
Timber.w("checkAndStartKeysBackup: invalid state: $state")
return
}
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
getCurrentVersion(object : MatrixCallback<KeysVersionResult?> {
override fun onSuccess(data: KeysVersionResult?) {
checkAndStartWithKeysBackupVersion(data)
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
keysBackupStateManager.state = KeysBackupState.Unknown
}
})
}
private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}")
keysBackupVersion = keyBackupVersion
if (keyBackupVersion == null) {
Timber.v("checkAndStartWithKeysBackupVersion: Found no key backup version on the homeserver")
resetKeysBackupData()
keysBackupStateManager.state = KeysBackupState.Disabled
} else {
getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> {
override fun onSuccess(data: KeysBackupVersionTrust) {
val versionInStore = getKeyBackupRecoveryKeyInfo()?.version
if (data.usable) {
Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}")
// Check the version we used at the previous app run
if (versionInStore != null && versionInStore != keyBackupVersion.version) {
Timber.v(" -> clean the previously used version $versionInStore")
resetKeysBackupData()
}
Timber.v(" -> enabling key backups")
// TODO
// enableKeysBackup(keyBackupVersion)
} else {
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
if (versionInStore != null) {
Timber.v(" -> disabling key backup")
resetKeysBackupData()
}
keysBackupStateManager.state = KeysBackupState.NotTrusted
}
}
override fun onFailure(failure: Throwable) {
// Cannot happen
}
})
}
}
override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) {
val keysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) }
try {
val key = BackupRecoveryKey.fromBase64(recoveryKey)
val publicKey = key.publicKey().publicKey
val authData = getMegolmBackupAuthData(keysBackupVersion) ?: return Unit.also { callback.onSuccess(false) }
callback.onSuccess(authData.publicKey == publicKey)
} catch (error: Throwable) {
callback.onFailure(error)
}
}
override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
val info = olmMachine.getBackupKeys() ?: return null
return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
}
/**
* Extract MegolmBackupAuthData data from a backup version.
*
* @param keysBackupData the key backup data
*
* @return the authentication if found and valid, null in other case
*/
private fun getMegolmBackupAuthData(keysBackupData: KeysVersionResult): MegolmBackupAuthData? {
return keysBackupData
.takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP }
?.getAuthDataAsMegolmBackupAuthData()
?.takeIf { it.publicKey.isNotEmpty() }
}
}

View file

@ -18,19 +18,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.12.0"
pbkdf2 = "0.8.0"
sha2 = "0.9.5"
rand = "0.8.4"
hmac = "0.11.0"
[dependencies.js_int] [dependencies.js_int]
version = "0.2.1" version = "0.2.1"
features = ["lax_deserialize"] features = ["lax_deserialize"]
[dependencies.matrix-sdk-common] [dependencies.matrix-sdk-common]
git = "https://github.com/matrix-org/matrix-rust-sdk/" path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-common/"
rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02" # git = "https://github.com/matrix-org/matrix-rust-sdk/"
# 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"
features = ["sled_cryptostore"] path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-crypto/"
features = ["sled_cryptostore", "qrcode", "backups_v1"]
[dependencies.tokio] [dependencies.tokio]
version = "1.7.1" version = "1.7.1"
@ -38,8 +44,9 @@ default_features = false
features = ["rt-multi-thread"] features = ["rt-multi-thread"]
[dependencies.ruma] [dependencies.ruma]
version = "0.3.0" git = "https://github.com/ruma/ruma"
features = ["client-api"] rev = "0101e110f"
features = ["client-api-c"]
[build-dependencies] [build-dependencies]
uniffi_build = "0.12.0" uniffi_build = "0.12.0"

View file

@ -0,0 +1,117 @@
use hmac::Hmac;
use pbkdf2::pbkdf2;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use sha2::Sha512;
use std::{collections::HashMap, iter};
use matrix_sdk_crypto::backups::RecoveryKey;
/// TODO
pub struct BackupRecoveryKey {
pub(crate) inner: RecoveryKey,
passphrase_info: Option<PassphraseInfo>,
}
/// TODO
#[derive(Debug, Clone)]
pub struct PassphraseInfo {
/// TODO
pub private_key_salt: String,
/// TODO
pub private_key_iterations: i32,
}
/// TODO
pub struct BackupKey {
/// TODO
pub public_key: String,
/// TODO
pub signatures: HashMap<String, HashMap<String, String>>,
/// TODO
pub passphrase_info: Option<PassphraseInfo>,
}
impl BackupRecoveryKey {
const KEY_SIZE: usize = 32;
const SALT_SIZE: usize = 32;
const PBKDF_ROUNDS: u32 = 500_000;
/// TODO
pub fn new() -> Self {
Self {
inner: RecoveryKey::new()
.expect("Can't gather enough randomness to create a recovery key"),
passphrase_info: None,
}
}
/// TODO
pub fn from_base64(key: String) -> Self {
Self {
inner: RecoveryKey::from_base64(key).unwrap(),
passphrase_info: None,
}
}
/// TODO
pub fn from_base58(key: String) -> Self {
Self {
inner: RecoveryKey::from_base58(&key).unwrap(),
passphrase_info: None,
}
}
/// TODO
pub fn from_passphrase(passphrase: String) -> Self {
let mut key = [0u8; Self::KEY_SIZE];
let mut rng = thread_rng();
let salt: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(Self::SALT_SIZE)
.collect();
pbkdf2::<Hmac<Sha512>>(
passphrase.as_bytes(),
salt.as_bytes(),
Self::PBKDF_ROUNDS,
&mut key,
);
Self {
inner: RecoveryKey::from_bytes(key),
passphrase_info: Some(PassphraseInfo {
private_key_salt: salt,
private_key_iterations: Self::PBKDF_ROUNDS as i32,
}),
}
}
/// TODO
pub fn public_key(&self) -> BackupKey {
let public_key = self.inner.public_key();
let signatures: HashMap<String, HashMap<String, String>> = public_key
.signatures()
.into_iter()
.map(|(k, v)| {
(
k.to_string(),
v.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
)
})
.collect();
BackupKey {
public_key: public_key.encoded_key(),
signatures,
passphrase_info: self.passphrase_info.clone(),
}
}
/// TODO
pub fn to_base58(&self) -> String {
self.inner.to_base58()
}
}

View file

@ -10,6 +10,7 @@
//! TODO //! TODO
mod backup_recovery_key;
mod device; mod device;
mod error; mod error;
mod logger; mod logger;
@ -18,18 +19,21 @@ mod responses;
mod users; mod users;
mod verification; mod verification;
pub use backup_recovery_key::{BackupKey, BackupRecoveryKey, PassphraseInfo};
pub use device::Device; pub use device::Device;
pub use error::{CryptoStoreError, DecryptionError, KeyImportError, SignatureError, SecretImportError}; pub use error::{
CryptoStoreError, DecryptionError, KeyImportError, SecretImportError, SignatureError,
};
pub use logger::{set_logger, Logger}; pub use logger::{set_logger, Logger};
pub use machine::{KeyRequestPair, OlmMachine}; pub use machine::{KeyRequestPair, OlmMachine};
pub use responses::{ pub use responses::{
DeviceLists, KeysImportResult, OutgoingVerificationRequest, Request, RequestType, SignatureUploadRequest, BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
BootstrapCrossSigningResult, UploadSigningKeysRequest, Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
}; };
pub use users::UserIdentity; pub use users::UserIdentity;
pub use verification::{ pub use verification::{
CancelInfo, QrCode, RequestVerificationResult, Sas, ScanResult, StartSasResult, Verification, CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, ScanResult,
VerificationRequest, ConfirmVerificationResult, StartSasResult, Verification, VerificationRequest,
}; };
/// Callback that will be passed over the FFI to report progress /// Callback that will be passed over the FFI to report progress
@ -83,6 +87,42 @@ pub struct CrossSigningKeyExport {
pub user_signing_key: Option<String>, pub user_signing_key: Option<String>,
} }
/// TODO
pub struct RoomKeyCounts {
/// TODO
pub total: i64,
/// TODO
pub backed_up: i64,
}
/// TODO
pub struct BackupKeys {
/// TODO
pub recovery_key: String,
/// TODO
pub backup_version: String,
}
impl std::convert::TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
type Error = ();
fn try_from(keys: matrix_sdk_crypto::store::BackupKeys) -> Result<Self, Self::Error> {
Ok(Self {
recovery_key: keys.recovery_key.ok_or(())?.to_base64(),
backup_version: keys.backup_version.ok_or(())?,
})
}
}
impl From<matrix_sdk_crypto::store::RoomKeyCounts> for RoomKeyCounts {
fn from(count: matrix_sdk_crypto::store::RoomKeyCounts) -> Self {
Self {
total: count.total as i64,
backed_up: count.backed_up as i64,
}
}
}
impl From<matrix_sdk_crypto::CrossSigningKeyExport> for CrossSigningKeyExport { impl From<matrix_sdk_crypto::CrossSigningKeyExport> for CrossSigningKeyExport {
fn from(e: matrix_sdk_crypto::CrossSigningKeyExport) -> Self { fn from(e: matrix_sdk_crypto::CrossSigningKeyExport) -> Self {
Self { Self {

View file

@ -9,6 +9,7 @@ use js_int::UInt;
use ruma::{ use ruma::{
api::{ api::{
client::r0::{ client::r0::{
backup::add_backup_keys::Response as KeysBackupResponse,
keys::{ keys::{
claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse, claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse,
upload_keys::Response as KeysUploadResponse, upload_keys::Response as KeysUploadResponse,
@ -31,17 +32,21 @@ use tokio::runtime::Runtime;
use matrix_sdk_common::{deserialized_responses::AlgorithmInfo, uuid::Uuid}; use matrix_sdk_common::{deserialized_responses::AlgorithmInfo, uuid::Uuid};
use matrix_sdk_crypto::{ use matrix_sdk_crypto::{
decrypt_key_export, encrypt_key_export, matrix_qrcode::QrVerificationData, EncryptionSettings, backups::{MegolmV1BackupKey, RecoveryKey},
LocalTrust, OlmMachine as InnerMachine, UserIdentities, Verification as RustVerification, decrypt_key_export, encrypt_key_export,
matrix_qrcode::QrVerificationData,
EncryptionSettings, LocalTrust, OlmMachine as InnerMachine, UserIdentities,
Verification as RustVerification,
}; };
use crate::{ use crate::{
error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError}, error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError},
responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse}, responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse},
BootstrapCrossSigningResult, ConfirmVerificationResult, CrossSigningKeyExport, BackupKey, BackupKeys, BootstrapCrossSigningResult, ConfirmVerificationResult,
CrossSigningStatus, DecryptedEvent, Device, DeviceLists, KeyImportError, KeysImportResult, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, Device, DeviceLists, KeyImportError,
ProgressListener, QrCode, Request, RequestType, RequestVerificationResult, ScanResult, KeysImportResult, ProgressListener, QrCode, Request, RequestType, RequestVerificationResult,
SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, RoomKeyCounts, ScanResult, SignatureUploadRequest, StartSasResult, UserIdentity, Verification,
VerificationRequest,
}; };
/// A high level state machine that handles E2EE for Matrix. /// A high level state machine that handles E2EE for Matrix.
@ -95,7 +100,7 @@ impl OlmMachine {
/// Get the display name of our own device. /// Get the display name of our own device.
pub fn display_name(&self) -> Result<Option<String>, CryptoStoreError> { pub fn display_name(&self) -> Result<Option<String>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.dislpay_name())?) Ok(self.runtime.block_on(self.inner.display_name())?)
} }
/// Get a cross signing user identity for the given user ID. /// Get a cross signing user identity for the given user ID.
@ -305,6 +310,9 @@ impl OlmMachine {
RequestType::SignatureUpload => { RequestType::SignatureUpload => {
SignatureUploadResponse::try_from_http_response(response).map(Into::into) SignatureUploadResponse::try_from_http_response(response).map(Into::into)
} }
RequestType::KeysBackup => {
KeysBackupResponse::try_from_http_response(response).map(Into::into)
}
} }
.expect("Can't convert json string to response"); .expect("Can't convert json string to response");
@ -701,10 +709,7 @@ impl OlmMachine {
methods: Vec<String>, methods: Vec<String>,
) -> Option<OutgoingVerificationRequest> { ) -> Option<OutgoingVerificationRequest> {
let user_id = UserId::try_from(user_id).ok()?; let user_id = UserId::try_from(user_id).ok()?;
let methods = methods let methods = methods.into_iter().map(VerificationMethod::from).collect();
.into_iter()
.map(VerificationMethod::from)
.collect();
if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) { if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) {
verification.accept_with_methods(methods).map(|r| r.into()) verification.accept_with_methods(methods).map(|r| r.into())
@ -731,10 +736,7 @@ impl OlmMachine {
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?; let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
let methods = methods let methods = methods.into_iter().map(VerificationMethod::from).collect();
.into_iter()
.map(VerificationMethod::from)
.collect();
Ok(if let Some(identity) = identity.and_then(|i| i.other()) { Ok(if let Some(identity) = identity.and_then(|i| i.other()) {
let content = self let content = self
@ -779,10 +781,7 @@ impl OlmMachine {
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?; let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
let methods = methods let methods = methods.into_iter().map(VerificationMethod::from).collect();
.into_iter()
.map(VerificationMethod::from)
.collect();
Ok(if let Some(identity) = identity.and_then(|i| i.other()) { Ok(if let Some(identity) = identity.and_then(|i| i.other()) {
let request = self.runtime.block_on(identity.request_verification( let request = self.runtime.block_on(identity.request_verification(
@ -816,10 +815,7 @@ impl OlmMachine {
) -> Result<Option<RequestVerificationResult>, CryptoStoreError> { ) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
let user_id = UserId::try_from(user_id)?; let user_id = UserId::try_from(user_id)?;
let methods = methods let methods = methods.into_iter().map(VerificationMethod::from).collect();
.into_iter()
.map(VerificationMethod::from)
.collect();
Ok( Ok(
if let Some(device) = self if let Some(device) = self
@ -854,10 +850,7 @@ impl OlmMachine {
.runtime .runtime
.block_on(self.inner.get_identity(self.inner.user_id()))?; .block_on(self.inner.get_identity(self.inner.user_id()))?;
let methods = methods let methods = methods.into_iter().map(VerificationMethod::from).collect();
.into_iter()
.map(VerificationMethod::from)
.collect();
Ok(if let Some(identity) = identity.and_then(|i| i.own()) { Ok(if let Some(identity) = identity.and_then(|i| i.own()) {
let (verification, request) = self let (verification, request) = self
@ -1023,10 +1016,7 @@ impl OlmMachine {
let user_id = UserId::try_from(user_id).ok()?; let user_id = UserId::try_from(user_id).ok()?;
self.inner self.inner
.get_verification(&user_id, flow_id) .get_verification(&user_id, flow_id)
.and_then(|v| { .and_then(|v| v.qr_v1().and_then(|qr| qr.to_bytes().map(encode).ok()))
v.qr_v1()
.and_then(|qr| qr.to_bytes().map(encode).ok())
})
} }
/// Pass data from a scanned QR code to an active verification request and /// Pass data from a scanned QR code to an active verification request and
@ -1254,4 +1244,74 @@ impl OlmMachine {
Ok(()) Ok(())
} }
/// TODO
pub fn enable_backup(&self, key: BackupKey, version: String) -> Result<(), CryptoStoreError> {
let backup_key = MegolmV1BackupKey::from_base64(&key.public_key).unwrap();
backup_key.set_version(version);
self.runtime
.block_on(self.inner.backup_machine().enable_backup(backup_key))?;
Ok(())
}
/// TODO
pub fn disable_backup(&self) -> Result<(), CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.backup_machine().disable_backup())?)
}
/// TODO
pub fn backup_room_keys(&self) -> Result<Option<Request>, CryptoStoreError> {
let request = self
.runtime
.block_on(self.inner.backup_machine().backup())?;
Ok(request.map(|r| r.into()))
}
/// TODO
pub fn room_key_counts(&self) -> Result<RoomKeyCounts, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.backup_machine().room_key_counts())?
.into())
}
/// TODO
pub fn save_recovery_key(
&self,
key: Option<String>,
version: Option<String>,
) -> Result<(), CryptoStoreError> {
let key = key.map(RecoveryKey::from_base64).transpose().ok().flatten();
Ok(self
.runtime
.block_on(self.inner.backup_machine().save_recovery_key(key, version))?)
}
/// TODO
pub fn get_backup_keys(&self) -> Result<Option<BackupKeys>, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.backup_machine().get_backup_keys())?
.try_into()
.ok())
}
/// TODO
pub fn sign(&self, message: &str) -> HashMap<String, HashMap<String, String>> {
self.runtime
.block_on(self.inner.sign(message))
.into_iter()
.map(|(k, v)| {
(
k.to_string(),
v.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
)
})
.collect()
}
} }

View file

@ -208,6 +208,7 @@ interface Request {
KeysUpload(string request_id, string body); KeysUpload(string request_id, string body);
KeysQuery(string request_id, sequence<string> users); KeysQuery(string request_id, sequence<string> users);
KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys); KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys);
KeysBackup(string request_id, record<DOMString, record<DOMString, string>> rooms);
RoomMessage(string request_id, string room_id, string event_type, string content); RoomMessage(string request_id, string room_id, string event_type, string content);
SignatureUpload(string request_id, string body); SignatureUpload(string request_id, string body);
}; };
@ -222,6 +223,7 @@ enum RequestType {
"KeysUpload", "KeysUpload",
"ToDevice", "ToDevice",
"SignatureUpload", "SignatureUpload",
"KeysBackup",
}; };
interface OlmMachine { interface OlmMachine {
@ -343,4 +345,49 @@ interface OlmMachine {
void import_cross_signing_keys(CrossSigningKeyExport export); void import_cross_signing_keys(CrossSigningKeyExport export);
[Throws=CryptoStoreError] [Throws=CryptoStoreError]
boolean is_identity_verified([ByRef] string user_id); boolean is_identity_verified([ByRef] string user_id);
record<DOMString, record<DOMString, string>> sign([ByRef] string message);
[Throws=CryptoStoreError]
void enable_backup(BackupKey key, string version);
[Throws=CryptoStoreError]
void disable_backup();
[Throws=CryptoStoreError]
Request? backup_room_keys();
[Throws=CryptoStoreError]
void save_recovery_key(string? key, string? version);
[Throws=CryptoStoreError]
RoomKeyCounts room_key_counts();
[Throws=CryptoStoreError]
BackupKeys? get_backup_keys();
};
dictionary PassphraseInfo {
string private_key_salt;
i32 private_key_iterations;
};
dictionary BackupKey {
string public_key;
record<DOMString, record<DOMString, string>> signatures;
PassphraseInfo? passphrase_info;
};
dictionary BackupKeys {
string recovery_key;
string backup_version;
};
dictionary RoomKeyCounts {
i64 total;
i64 backed_up;
};
interface BackupRecoveryKey {
constructor();
[Name=from_base64]
constructor(string key);
[Name=from_passphrase]
constructor(string key);
string to_base58();
BackupKey public_key();
}; };

View file

@ -8,6 +8,7 @@ use serde_json::json;
use ruma::{ use ruma::{
api::client::r0::{ api::client::r0::{
backup::add_backup_keys::Response as KeysBackupResponse,
keys::{ keys::{
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse}, claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
get_keys::Response as KeysQueryResponse, get_keys::Response as KeysQueryResponse,
@ -152,6 +153,10 @@ pub enum Request {
request_id: String, request_id: String,
body: String, body: String,
}, },
KeysBackup {
request_id: String,
rooms: HashMap<String, HashMap<String, String>>,
}
} }
impl From<OutgoingRequest> for Request { impl From<OutgoingRequest> for Request {
@ -186,6 +191,7 @@ impl From<OutgoingRequest> for Request {
}, },
RoomMessage(r) => Request::from(r), RoomMessage(r) => Request::from(r),
KeysClaim(c) => (*r.request_id(), c.clone()).into(), KeysClaim(c) => (*r.request_id(), c.clone()).into(),
KeysBackup(_) => todo!(),
} }
} }
} }
@ -256,6 +262,7 @@ pub enum RequestType {
KeysUpload, KeysUpload,
ToDevice, ToDevice,
SignatureUpload, SignatureUpload,
KeysBackup,
} }
pub struct DeviceLists { pub struct DeviceLists {
@ -291,6 +298,7 @@ pub(crate) enum OwnedResponse {
KeysQuery(KeysQueryResponse), KeysQuery(KeysQueryResponse),
ToDevice(ToDeviceResponse), ToDevice(ToDeviceResponse),
SignatureUpload(SignatureUploadResponse), SignatureUpload(SignatureUploadResponse),
KeysBackup(KeysBackupResponse),
} }
impl From<KeysClaimResponse> for OwnedResponse { impl From<KeysClaimResponse> for OwnedResponse {
@ -323,6 +331,12 @@ impl From<SignatureUploadResponse> for OwnedResponse {
} }
} }
impl From<KeysBackupResponse> for OwnedResponse {
fn from(r: KeysBackupResponse) -> Self {
Self::KeysBackup(r)
}
}
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> { impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
fn from(r: &'a OwnedResponse) -> Self { fn from(r: &'a OwnedResponse) -> Self {
match r { match r {
@ -331,6 +345,7 @@ impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
OwnedResponse::KeysUpload(r) => IncomingResponse::KeysUpload(r), OwnedResponse::KeysUpload(r) => IncomingResponse::KeysUpload(r),
OwnedResponse::ToDevice(r) => IncomingResponse::ToDevice(r), OwnedResponse::ToDevice(r) => IncomingResponse::ToDevice(r),
OwnedResponse::SignatureUpload(r) => IncomingResponse::SignatureUpload(r), OwnedResponse::SignatureUpload(r) => IncomingResponse::SignatureUpload(r),
OwnedResponse::KeysBackup(r) => IncomingResponse::KeysBackup(r),
} }
} }
} }

View file

@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import timber.log.Timber
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
session: Session session: Session
@ -81,6 +82,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
private fun getKeysBackupTrust() = withState { state -> private fun getKeysBackupTrust() = withState { state ->
val versionResult = keysBackupService.keysBackupVersion val versionResult = keysBackupService.keysBackupVersion
Timber.d("BACKUP: HEEEEEEE $versionResult ${state.keysBackupVersionTrust}")
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) { if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
setState { setState {
@ -89,10 +91,12 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
deleteBackupRequest = Uninitialized deleteBackupRequest = Uninitialized
) )
} }
Timber.d("BACKUP: HEEEEEEE TWO")
keysBackupService keysBackupService
.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> { .getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
override fun onSuccess(data: KeysBackupVersionTrust) { override fun onSuccess(data: KeysBackupVersionTrust) {
Timber.d("BACKUP: HEEEE suceeeded $data")
setState { setState {
copy( copy(
keysBackupVersionTrust = Success(data) keysBackupVersionTrust = Success(data)
@ -101,6 +105,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
Timber.d("BACKUP: HEEEE FAILED $failure")
setState { setState {
copy( copy(
keysBackupVersionTrust = Fail(failure) keysBackupVersionTrust = Fail(failure)