Fix unneeded re-uploade of key got from backup

and disabled prompting for untrusted key sharing
This commit is contained in:
Valere 2022-03-29 15:56:34 +02:00
parent cc107498eb
commit 88cf1a5e67
5 changed files with 183 additions and 135 deletions

View file

@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
@ -168,6 +169,11 @@ internal class OutgoingKeyRequestManager @Inject constructor(
sequencer.post {
cryptoStore.deleteOutgoingRoomKeyRequestInState(OutgoingRoomKeyRequestState.SENT)
}
sequencer.post {
delay(1000)
perSessionBackupQueryRateLimiter.refreshBackupInfoIfNeeded(true)
}
}
}
}

View file

@ -68,7 +68,7 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor(
var backupWasCheckedFromServer: Boolean = false
var now = System.currentTimeMillis()
private fun refreshBackupInfoIfNeeded(force: Boolean = false) {
fun refreshBackupInfoIfNeeded(force: Boolean = false) {
if (backupWasCheckedFromServer && !force) return
Timber.tag(loggerTag.value).v("Checking if can access a backup")
backupWasCheckedFromServer = true

View file

@ -63,16 +63,11 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBack
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
@ -112,16 +107,11 @@ internal class DefaultKeysBackupService @Inject constructor(
// Tasks
private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
private val deleteBackupTask: DeleteBackupTask,
private val deleteRoomSessionDataTask: DeleteRoomSessionDataTask,
private val deleteRoomSessionsDataTask: DeleteRoomSessionsDataTask,
private val deleteSessionDataTask: DeleteSessionsDataTask,
private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask,
private val getKeysBackupVersionTask: GetKeysBackupVersionTask,
private val getRoomSessionDataTask: GetRoomSessionDataTask,
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
private val getSessionsDataTask: GetSessionsDataTask,
private val storeRoomSessionDataTask: StoreRoomSessionDataTask,
private val storeSessionsDataTask: StoreRoomSessionsDataTask,
private val storeSessionDataTask: StoreSessionsDataTask,
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
// Task executor
@ -168,58 +158,63 @@ internal class DefaultKeysBackupService @Inject constructor(
override fun prepareKeysBackupVersion(password: String?,
progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
val olmPkDecryption = OlmPkDecryption()
val signalableMegolmBackupAuthData = if (password != null) {
// Generate a private key from the password
val backgroundProgressListener = if (progressListener == null) {
null
} else {
object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
uiHandler.post {
try {
progressListener.onProgress(progress, total)
} catch (e: Exception) {
Timber.e(e, "prepareKeysBackupVersion: onProgress failure")
}
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
try {
val olmPkDecryption = OlmPkDecryption()
val signalableMegolmBackupAuthData = if (password != null) {
// Generate a private key from the password
val backgroundProgressListener = if (progressListener == null) {
null
} else {
object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
uiHandler.post {
try {
progressListener.onProgress(progress, total)
} catch (e: Exception) {
Timber.e(e, "prepareKeysBackupVersion: onProgress failure")
}
}
}
}
val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
SignalableMegolmBackupAuthData(
publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
privateKeySalt = generatePrivateKeyResult.salt,
privateKeyIterations = generatePrivateKeyResult.iterations
)
} else {
val publicKey = olmPkDecryption.generateKey()
SignalableMegolmBackupAuthData(
publicKey = publicKey
)
}
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary())
val signedMegolmBackupAuthData = MegolmBackupAuthData(
publicKey = signalableMegolmBackupAuthData.publicKey,
privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt,
privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations,
signatures = objectSigner.signObject(canonicalJson)
val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
SignalableMegolmBackupAuthData(
publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
privateKeySalt = generatePrivateKeyResult.salt,
privateKeyIterations = generatePrivateKeyResult.iterations
)
} else {
val publicKey = olmPkDecryption.generateKey()
MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = signedMegolmBackupAuthData,
recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
SignalableMegolmBackupAuthData(
publicKey = publicKey
)
}
}.foldToCallback(callback)
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary())
val signedMegolmBackupAuthData = MegolmBackupAuthData(
publicKey = signalableMegolmBackupAuthData.publicKey,
privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt,
privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations,
signatures = objectSigner.signObject(canonicalJson)
)
val creationInfo = MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = signedMegolmBackupAuthData,
recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
)
uiHandler.post {
callback.onSuccess(creationInfo)
}
} catch (failure: Throwable) {
uiHandler.post {
callback.onFailure(failure)
}
}
}
}
@ -267,41 +262,39 @@ internal class DefaultKeysBackupService @Inject constructor(
}
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.crypto) {
// If we're currently backing up to this backup... stop.
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
if (keysBackupVersion != null && version == keysBackupVersion?.version) {
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Unknown
}
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
// If we're currently backing up to this backup... stop.
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
if (keysBackupVersion != null && version == keysBackupVersion?.version) {
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Unknown
}
deleteBackupTask
.configureWith(DeleteBackupTask.Params(version)) {
this.callback = object : MatrixCallback<Unit> {
private fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
}
}
override fun onSuccess(data: Unit) {
eventuallyRestartBackup()
uiHandler.post { callback?.onSuccess(Unit) }
}
override fun onFailure(failure: Throwable) {
eventuallyRestartBackup()
uiHandler.post { callback?.onFailure(failure) }
deleteBackupTask
.configureWith(DeleteBackupTask.Params(version)) {
this.callback = object : MatrixCallback<Unit> {
private fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
}
}
override fun onSuccess(data: Unit) {
eventuallyRestartBackup()
uiHandler.post { callback?.onSuccess(Unit) }
}
override fun onFailure(failure: Throwable) {
eventuallyRestartBackup()
uiHandler.post { callback?.onFailure(failure) }
}
}
.executeBy(taskExecutor)
}
}
.executeBy(taskExecutor)
}
}
@ -480,10 +473,11 @@ internal class DefaultKeysBackupService @Inject constructor(
if (authData == null) {
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
callback.onFailure(IllegalArgumentException("Missing element"))
uiHandler.post {
callback.onFailure(IllegalArgumentException("Missing element"))
}
} else {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
// Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
@ -535,11 +529,15 @@ internal class DefaultKeysBackupService @Inject constructor(
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
callback.onSuccess(data)
uiHandler.post {
callback.onSuccess(data)
}
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
uiHandler.post {
callback.onFailure(failure)
}
}
}
}
@ -553,15 +551,14 @@ internal class DefaultKeysBackupService @Inject constructor(
callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val isValid = withContext(coroutineDispatchers.crypto) {
isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
}
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
val isValid = isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
if (!isValid) {
Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.")
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
uiHandler.post {
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
}
} else {
trustKeysBackupVersion(keysBackupVersion, true, callback)
}
@ -573,15 +570,14 @@ internal class DefaultKeysBackupService @Inject constructor(
callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val recoveryKey = withContext(coroutineDispatchers.crypto) {
recoveryKeyFromPassword(password, keysBackupVersion, null)
}
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null)
if (recoveryKey == null) {
Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")
callback.onFailure(IllegalArgumentException("Missing element"))
uiHandler.post {
callback.onFailure(IllegalArgumentException("Missing element"))
}
} else {
// Check trust using the recovery key
trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
@ -592,30 +588,23 @@ internal class DefaultKeysBackupService @Inject constructor(
override fun onSecretKeyGossip(secret: String) {
Timber.i("## CrossSigning - onSecretKeyGossip")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
try {
when (val keysBackupLastVersionResult = getKeysBackupLastVersionTask.execute(Unit)) {
KeysBackupLastVersionResult.NoKeysBackup -> {
Timber.d("No keys backup found")
}
is KeysBackupLastVersionResult.KeysBackup -> {
val keysBackupVersion = keysBackupLastVersionResult.keysVersionResult
val recoveryKey = computeRecoveryKey(secret.fromBase64())
if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) {
awaitCallback<Unit> {
trustKeysBackupVersion(keysBackupVersion, true, it)
}
val importResult = awaitCallback<ImportRoomKeysResult> {
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
}
withContext(coroutineDispatchers.crypto) {
cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
}
Timber.i("onSecretKeyGossip: Recovered keys $importResult")
} else {
Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}")
}
// we don't want to start immediately downloading all as it can take very long
// val importResult = awaitCallback<ImportRoomKeysResult> {
// restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
// }
withContext(coroutineDispatchers.crypto) {
cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
}
Timber.i("onSecretKeyGossip: saved valid backup key")
} else {
Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}")
}
} catch (failure: Throwable) {
Timber.e("onSecretKeyGossip: failed to trust key backup version ${keysBackupVersion?.version}")
@ -678,9 +667,9 @@ internal class DefaultKeysBackupService @Inject constructor(
callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
runCatching {
val decryption = withContext(coroutineDispatchers.crypto) {
val decryption = withContext(coroutineDispatchers.computation) {
// Check if the recovery is valid before going any further
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
@ -749,7 +738,19 @@ internal class DefaultKeysBackupService @Inject constructor(
}
result
}
}.foldToCallback(callback)
}.foldToCallback(object : MatrixCallback<ImportRoomKeysResult> {
override fun onSuccess(data: ImportRoomKeysResult) {
uiHandler.post {
callback.onSuccess(data)
}
}
override fun onFailure(failure: Throwable) {
uiHandler.post {
callback.onFailure(failure)
}
}
})
}
}
@ -761,7 +762,7 @@ internal class DefaultKeysBackupService @Inject constructor(
callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
runCatching {
val progressListener = if (stepProgressListener != null) {
object : ProgressListener {
@ -786,7 +787,19 @@ internal class DefaultKeysBackupService @Inject constructor(
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, it)
}
}
}.foldToCallback(callback)
}.foldToCallback(object : MatrixCallback<ImportRoomKeysResult> {
override fun onSuccess(data: ImportRoomKeysResult) {
uiHandler.post {
callback.onSuccess(data)
}
}
override fun onFailure(failure: Throwable) {
uiHandler.post {
callback.onFailure(failure)
}
}
})
}
}

View file

@ -740,14 +740,23 @@ internal class RealmCryptoStore @Inject constructor(
if (sessionIdentifier != null) {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey)
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
primaryKey = key
sessionId = sessionIdentifier
senderKey = session.senderKey
putInboundGroupSession(session)
}
val existing = realm.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findFirst()
realm.insertOrUpdate(realmOlmInboundGroupSession)
if (existing != null) {
// we want to keep the existing backup status
existing.putInboundGroupSession(session)
} else {
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
primaryKey = key
sessionId = sessionIdentifier
senderKey = session.senderKey
putInboundGroupSession(session)
}
realm.insertOrUpdate(realmOlmInboundGroupSession)
}
}
}
}
@ -876,17 +885,32 @@ internal class RealmCryptoStore @Inject constructor(
return
}
doRealmTransaction(realmConfiguration) {
doRealmTransaction(realmConfiguration) { realm ->
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
try {
val sessionIdentifier = olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier()
val key = OlmInboundGroupSessionEntity.createPrimaryKey(
olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier(),
sessionIdentifier,
olmInboundGroupSessionWrapper.senderKey)
it.where<OlmInboundGroupSessionEntity>()
val existing = realm.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findFirst()
?.backedUp = true
if (existing != null) {
existing.backedUp = true
} else {
// ... might be in cache but not yet persisted, create a record to persist backedup state
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
primaryKey = key
sessionId = sessionIdentifier
senderKey = olmInboundGroupSessionWrapper.senderKey
putInboundGroupSession(olmInboundGroupSessionWrapper)
backedUp = true
}
realm.insertOrUpdate(realmOlmInboundGroupSession)
}
} catch (e: OlmException) {
Timber.e(e, "OlmException")
}

View file

@ -68,6 +68,9 @@ class KeyRequestHandler @Inject constructor(
var session: Session? = null
// This functionality is disabled in element for now. As it could be prone to social attacks
var enablePromptingForRequest = false
fun start(session: Session) {
this.session = session
session.cryptoService().verificationService().addListener(this)
@ -92,6 +95,8 @@ class KeyRequestHandler @Inject constructor(
* @param request the key request.
*/
override fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
if (!enablePromptingForRequest) return
val userId = request.userId
val deviceId = request.deviceId
val requestId = request.requestId