crypto: Initial support for server-side backups of room keys
This commit is contained in:
parent
d3a761a73a
commit
406fd0d8d5
|
@ -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.extensions.tryOrNull
|
||||
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.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
|
@ -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.internal.auth.registration.handleUIA
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||
import org.matrix.android.sdk.internal.crypto.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.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||
|
@ -119,6 +128,10 @@ internal class RequestSender @Inject constructor(
|
|||
private val signaturesUploadTask: UploadSignaturesTask,
|
||||
private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>,
|
||||
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||
private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask,
|
||||
private val getKeysBackupVersionTask: GetKeysBackupVersionTask,
|
||||
private val deleteBackupTask: DeleteBackupTask,
|
||||
private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
|
||||
) {
|
||||
companion object {
|
||||
const val REQUEST_RETRY_COUNT = 3
|
||||
|
@ -192,7 +205,7 @@ internal class RequestSender @Inject constructor(
|
|||
request: UploadSigningKeysRequest,
|
||||
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 selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
|
||||
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
|
||||
|
@ -248,6 +261,32 @@ internal class RequestSender @Inject constructor(
|
|||
val sendToDeviceParams = SendToDeviceTask.Params(eventType, userMap, transactionId)
|
||||
sendToDeviceTask.executeRetry(sendToDeviceParams, REQUEST_RETRY_COUNT)
|
||||
}
|
||||
|
||||
suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? {
|
||||
return try {
|
||||
if (version != null) {
|
||||
getKeysBackupVersionTask.execute(version)
|
||||
} else {
|
||||
getKeysBackupLastVersionTask.execute(Unit)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is Failure.ServerError
|
||||
&& failure.error.code == MatrixError.M_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createKeyBackup(body: CreateKeysBackupVersionBody): KeysVersion {
|
||||
return createKeysBackupVersionTask.execute(body)
|
||||
}
|
||||
|
||||
suspend fun deleteKeyBackup(version: String) {
|
||||
val params = DeleteBackupTask.Params(version)
|
||||
deleteBackupTask.execute(params)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -272,8 +311,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val cryptoStore: IMXCryptoStore,
|
||||
// Set of parameters used to configure/customize the end-to-end crypto.
|
||||
private val mxCryptoConfig: MXCryptoConfig,
|
||||
// The key backup service.
|
||||
private val keysBackupService: DefaultKeysBackupService,
|
||||
// Actions
|
||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||
// Tasks
|
||||
|
@ -283,6 +320,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val setDeviceNameTask: SetDeviceNameTask,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||
private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
|
@ -300,6 +338,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// The cross signing service.
|
||||
private var crossSigningService: RustCrossSigningService? = null
|
||||
|
||||
// The key backup service.
|
||||
private var keysBackupService: RustKeyBackupService? = null
|
||||
|
||||
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
||||
|
||||
// Locks for some of our operations
|
||||
|
@ -448,9 +489,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
cryptoStore.open()
|
||||
|
||||
// this can throw if no backup
|
||||
/*
|
||||
TODO
|
||||
tryOrNull {
|
||||
keysBackupService.checkAndStartKeysBackup()
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,6 +510,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
olmMachine = machine
|
||||
verificationService = RustVerificationService(machine)
|
||||
crossSigningService = RustCrossSigningService(machine)
|
||||
keysBackupService = RustKeyBackupService(machine, sender, coroutineDispatchers, cryptoCoroutineScope)
|
||||
Timber.v(
|
||||
"## CRYPTO | Successfully started up an Olm machine for " +
|
||||
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
|
||||
|
@ -473,6 +518,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
Timber.v("Failed create an Olm machine: $throwable")
|
||||
}
|
||||
|
||||
tryOrNull {
|
||||
keysBackupService!!.checkAndStartKeysBackup()
|
||||
}
|
||||
|
||||
// Open the store
|
||||
cryptoStore.open()
|
||||
|
||||
|
@ -494,7 +543,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
/**
|
||||
* @return the Keys backup Service
|
||||
*/
|
||||
override fun keysBackupService() = keysBackupService
|
||||
override fun keysBackupService(): KeysBackupService {
|
||||
if (keysBackupService == null) {
|
||||
internalStart()
|
||||
}
|
||||
return keysBackupService!!
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the VerificationService
|
||||
|
@ -971,6 +1025,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
signatureUpload(it)
|
||||
}
|
||||
}
|
||||
is Request.KeysBackup -> {
|
||||
async {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
|
|
@ -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.ToDeviceSyncResponse
|
||||
import timber.log.Timber
|
||||
import uniffi.olm.BackupKey
|
||||
import uniffi.olm.BackupKeys
|
||||
import uniffi.olm.CrossSigningKeyExport
|
||||
import uniffi.olm.CrossSigningStatus
|
||||
import uniffi.olm.CryptoStoreErrorException
|
||||
|
@ -59,6 +61,7 @@ import uniffi.olm.OlmMachine as InnerMachine
|
|||
import uniffi.olm.ProgressListener as RustProgressListener
|
||||
import uniffi.olm.Request
|
||||
import uniffi.olm.RequestType
|
||||
import uniffi.olm.RoomKeyCounts
|
||||
import uniffi.olm.UserIdentity as RustUserIdentity
|
||||
import uniffi.olm.setLogger
|
||||
|
||||
|
@ -760,4 +763,33 @@ internal class OlmMachine(
|
|||
// TODO map the errors from importCrossSigningKeys to the UserTrustResult
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
|
@ -18,19 +18,25 @@ thiserror = "1.0.25"
|
|||
tracing = "0.1.26"
|
||||
tracing-subscriber = "0.2.18"
|
||||
uniffi = "0.12.0"
|
||||
pbkdf2 = "0.8.0"
|
||||
sha2 = "0.9.5"
|
||||
rand = "0.8.4"
|
||||
hmac = "0.11.0"
|
||||
|
||||
[dependencies.js_int]
|
||||
version = "0.2.1"
|
||||
features = ["lax_deserialize"]
|
||||
|
||||
[dependencies.matrix-sdk-common]
|
||||
git = "https://github.com/matrix-org/matrix-rust-sdk/"
|
||||
rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02"
|
||||
path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-common/"
|
||||
# git = "https://github.com/matrix-org/matrix-rust-sdk/"
|
||||
# rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02"
|
||||
|
||||
[dependencies.matrix-sdk-crypto]
|
||||
git = "https://github.com/matrix-org/matrix-rust-sdk/"
|
||||
rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02"
|
||||
features = ["sled_cryptostore"]
|
||||
# git = "https://github.com/matrix-org/matrix-rust-sdk/"
|
||||
# rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02"
|
||||
path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-crypto/"
|
||||
features = ["sled_cryptostore", "qrcode", "backups_v1"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.7.1"
|
||||
|
@ -38,8 +44,9 @@ default_features = false
|
|||
features = ["rt-multi-thread"]
|
||||
|
||||
[dependencies.ruma]
|
||||
version = "0.3.0"
|
||||
features = ["client-api"]
|
||||
git = "https://github.com/ruma/ruma"
|
||||
rev = "0101e110f"
|
||||
features = ["client-api-c"]
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = "0.12.0"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
//! TODO
|
||||
|
||||
mod backup_recovery_key;
|
||||
mod device;
|
||||
mod error;
|
||||
mod logger;
|
||||
|
@ -18,18 +19,21 @@ mod responses;
|
|||
mod users;
|
||||
mod verification;
|
||||
|
||||
pub use backup_recovery_key::{BackupKey, BackupRecoveryKey, PassphraseInfo};
|
||||
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 machine::{KeyRequestPair, OlmMachine};
|
||||
pub use responses::{
|
||||
DeviceLists, KeysImportResult, OutgoingVerificationRequest, Request, RequestType, SignatureUploadRequest,
|
||||
BootstrapCrossSigningResult, UploadSigningKeysRequest,
|
||||
BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
|
||||
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
|
||||
};
|
||||
pub use users::UserIdentity;
|
||||
pub use verification::{
|
||||
CancelInfo, QrCode, RequestVerificationResult, Sas, ScanResult, StartSasResult, Verification,
|
||||
VerificationRequest, ConfirmVerificationResult,
|
||||
CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, ScanResult,
|
||||
StartSasResult, Verification, VerificationRequest,
|
||||
};
|
||||
|
||||
/// Callback that will be passed over the FFI to report progress
|
||||
|
@ -83,6 +87,42 @@ pub struct CrossSigningKeyExport {
|
|||
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 {
|
||||
fn from(e: matrix_sdk_crypto::CrossSigningKeyExport) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -9,6 +9,7 @@ use js_int::UInt;
|
|||
use ruma::{
|
||||
api::{
|
||||
client::r0::{
|
||||
backup::add_backup_keys::Response as KeysBackupResponse,
|
||||
keys::{
|
||||
claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse,
|
||||
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_crypto::{
|
||||
decrypt_key_export, encrypt_key_export, matrix_qrcode::QrVerificationData, EncryptionSettings,
|
||||
LocalTrust, OlmMachine as InnerMachine, UserIdentities, Verification as RustVerification,
|
||||
backups::{MegolmV1BackupKey, RecoveryKey},
|
||||
decrypt_key_export, encrypt_key_export,
|
||||
matrix_qrcode::QrVerificationData,
|
||||
EncryptionSettings, LocalTrust, OlmMachine as InnerMachine, UserIdentities,
|
||||
Verification as RustVerification,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError},
|
||||
responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse},
|
||||
BootstrapCrossSigningResult, ConfirmVerificationResult, CrossSigningKeyExport,
|
||||
CrossSigningStatus, DecryptedEvent, Device, DeviceLists, KeyImportError, KeysImportResult,
|
||||
ProgressListener, QrCode, Request, RequestType, RequestVerificationResult, ScanResult,
|
||||
SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest,
|
||||
BackupKey, BackupKeys, BootstrapCrossSigningResult, ConfirmVerificationResult,
|
||||
CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, Device, DeviceLists, KeyImportError,
|
||||
KeysImportResult, ProgressListener, QrCode, Request, RequestType, RequestVerificationResult,
|
||||
RoomKeyCounts, ScanResult, SignatureUploadRequest, StartSasResult, UserIdentity, Verification,
|
||||
VerificationRequest,
|
||||
};
|
||||
|
||||
/// A high level state machine that handles E2EE for Matrix.
|
||||
|
@ -95,7 +100,7 @@ impl OlmMachine {
|
|||
|
||||
/// Get the display name of our own device.
|
||||
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.
|
||||
|
@ -305,6 +310,9 @@ impl OlmMachine {
|
|||
RequestType::SignatureUpload => {
|
||||
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");
|
||||
|
||||
|
@ -701,10 +709,7 @@ impl OlmMachine {
|
|||
methods: Vec<String>,
|
||||
) -> Option<OutgoingVerificationRequest> {
|
||||
let user_id = UserId::try_from(user_id).ok()?;
|
||||
let methods = methods
|
||||
.into_iter()
|
||||
.map(VerificationMethod::from)
|
||||
.collect();
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) {
|
||||
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 methods = methods
|
||||
.into_iter()
|
||||
.map(VerificationMethod::from)
|
||||
.collect();
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
Ok(if let Some(identity) = identity.and_then(|i| i.other()) {
|
||||
let content = self
|
||||
|
@ -779,10 +781,7 @@ impl OlmMachine {
|
|||
|
||||
let identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
|
||||
|
||||
let methods = methods
|
||||
.into_iter()
|
||||
.map(VerificationMethod::from)
|
||||
.collect();
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
Ok(if let Some(identity) = identity.and_then(|i| i.other()) {
|
||||
let request = self.runtime.block_on(identity.request_verification(
|
||||
|
@ -816,10 +815,7 @@ impl OlmMachine {
|
|||
) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
|
||||
let user_id = UserId::try_from(user_id)?;
|
||||
|
||||
let methods = methods
|
||||
.into_iter()
|
||||
.map(VerificationMethod::from)
|
||||
.collect();
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
Ok(
|
||||
if let Some(device) = self
|
||||
|
@ -854,10 +850,7 @@ impl OlmMachine {
|
|||
.runtime
|
||||
.block_on(self.inner.get_identity(self.inner.user_id()))?;
|
||||
|
||||
let methods = methods
|
||||
.into_iter()
|
||||
.map(VerificationMethod::from)
|
||||
.collect();
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
|
||||
Ok(if let Some(identity) = identity.and_then(|i| i.own()) {
|
||||
let (verification, request) = self
|
||||
|
@ -1023,10 +1016,7 @@ impl OlmMachine {
|
|||
let user_id = UserId::try_from(user_id).ok()?;
|
||||
self.inner
|
||||
.get_verification(&user_id, flow_id)
|
||||
.and_then(|v| {
|
||||
v.qr_v1()
|
||||
.and_then(|qr| qr.to_bytes().map(encode).ok())
|
||||
})
|
||||
.and_then(|v| 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
|
||||
|
@ -1254,4 +1244,74 @@ impl OlmMachine {
|
|||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,6 +208,7 @@ interface Request {
|
|||
KeysUpload(string request_id, string body);
|
||||
KeysQuery(string request_id, sequence<string> users);
|
||||
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);
|
||||
SignatureUpload(string request_id, string body);
|
||||
};
|
||||
|
@ -222,6 +223,7 @@ enum RequestType {
|
|||
"KeysUpload",
|
||||
"ToDevice",
|
||||
"SignatureUpload",
|
||||
"KeysBackup",
|
||||
};
|
||||
|
||||
interface OlmMachine {
|
||||
|
@ -343,4 +345,49 @@ interface OlmMachine {
|
|||
void import_cross_signing_keys(CrossSigningKeyExport export);
|
||||
[Throws=CryptoStoreError]
|
||||
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();
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ use serde_json::json;
|
|||
|
||||
use ruma::{
|
||||
api::client::r0::{
|
||||
backup::add_backup_keys::Response as KeysBackupResponse,
|
||||
keys::{
|
||||
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
|
||||
get_keys::Response as KeysQueryResponse,
|
||||
|
@ -152,6 +153,10 @@ pub enum Request {
|
|||
request_id: String,
|
||||
body: String,
|
||||
},
|
||||
KeysBackup {
|
||||
request_id: String,
|
||||
rooms: HashMap<String, HashMap<String, String>>,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutgoingRequest> for Request {
|
||||
|
@ -186,6 +191,7 @@ impl From<OutgoingRequest> for Request {
|
|||
},
|
||||
RoomMessage(r) => Request::from(r),
|
||||
KeysClaim(c) => (*r.request_id(), c.clone()).into(),
|
||||
KeysBackup(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +262,7 @@ pub enum RequestType {
|
|||
KeysUpload,
|
||||
ToDevice,
|
||||
SignatureUpload,
|
||||
KeysBackup,
|
||||
}
|
||||
|
||||
pub struct DeviceLists {
|
||||
|
@ -291,6 +298,7 @@ pub(crate) enum OwnedResponse {
|
|||
KeysQuery(KeysQueryResponse),
|
||||
ToDevice(ToDeviceResponse),
|
||||
SignatureUpload(SignatureUploadResponse),
|
||||
KeysBackup(KeysBackupResponse),
|
||||
}
|
||||
|
||||
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> {
|
||||
fn from(r: &'a OwnedResponse) -> Self {
|
||||
match r {
|
||||
|
@ -331,6 +345,7 @@ impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
|
|||
OwnedResponse::KeysUpload(r) => IncomingResponse::KeysUpload(r),
|
||||
OwnedResponse::ToDevice(r) => IncomingResponse::ToDevice(r),
|
||||
OwnedResponse::SignatureUpload(r) => IncomingResponse::SignatureUpload(r),
|
||||
OwnedResponse::KeysBackup(r) => IncomingResponse::KeysBackup(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.KeysBackupStateListener
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import timber.log.Timber
|
||||
|
||||
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
||||
session: Session
|
||||
|
@ -81,6 +82,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||
|
||||
private fun getKeysBackupTrust() = withState { state ->
|
||||
val versionResult = keysBackupService.keysBackupVersion
|
||||
Timber.d("BACKUP: HEEEEEEE $versionResult ${state.keysBackupVersionTrust}")
|
||||
|
||||
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
|
||||
setState {
|
||||
|
@ -89,10 +91,12 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||
deleteBackupRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
Timber.d("BACKUP: HEEEEEEE TWO")
|
||||
|
||||
keysBackupService
|
||||
.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
|
||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
||||
Timber.d("BACKUP: HEEEE suceeeded $data")
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Success(data)
|
||||
|
@ -101,6 +105,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.d("BACKUP: HEEEE FAILED $failure")
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Fail(failure)
|
||||
|
|
Loading…
Reference in New Issue