Fallback keys implementation.

Author: Onuray - Benoit squashes the 4 commit to cancel the addition on binaries
This commit is contained in:
Benoit Marty 2021-10-07 12:10:41 +02:00 committed by Valere
parent 7cf92ec17d
commit 4ac90f10c1
6 changed files with 84 additions and 15 deletions

View file

@ -58,6 +58,14 @@ data class SyncResponse(
@Json(name = "device_one_time_keys_count") @Json(name = "device_one_time_keys_count")
val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null, val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null,
/**
* The key algorithms for which the server has an unused fallback key for the device.
* If the client wants the server to have a fallback key for a given key algorithm,
* but that algorithm is not listed in device_unused_fallback_key_types, the client will upload a new key.
*/
@Json(name = "org.matrix.msc2732.device_unused_fallback_key_types")
val deviceUnusedFallbackKeyTypes: List<String>? = null,
/** /**
* List of groups. * List of groups.
*/ */

View file

@ -67,6 +67,7 @@ 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.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
@ -329,7 +330,7 @@ internal class DefaultCryptoService @Inject constructor(
uploadDeviceKeys() uploadDeviceKeys()
} }
oneTimeKeysUploader.maybeUploadOneTimeKeys() oneTimeKeysUploader.maybeUploadOneTimeKeys(shouldGenerateFallbackKey = false)
// this can throw if no backup // this can throw if no backup
tryOrNull { tryOrNull {
keysBackupService.checkAndStartKeysBackup() keysBackupService.checkAndStartKeysBackup()
@ -431,7 +432,17 @@ internal class DefaultCryptoService @Inject constructor(
if (isStarted()) { if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782 // Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists() deviceListManager.refreshOutdatedDeviceLists()
oneTimeKeysUploader.maybeUploadOneTimeKeys() // The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
// If there's no unused signed_curve25519 fallback key we need a new one.
val shouldGenerateFallbackKey = if (syncResponse.deviceUnusedFallbackKeyTypes != null) {
// Generate a fallback key only if the server does not already have an unused fallback key.
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)
} else {
// Server does not support fallbackKey
false
}
oneTimeKeysUploader.maybeUploadOneTimeKeys(shouldGenerateFallbackKey)
incomingGossipingRequestManager.processReceivedGossipingRequests() incomingGossipingRequestManager.processReceivedGossipingRequests()
} }
} }
@ -928,7 +939,7 @@ internal class DefaultCryptoService @Inject constructor(
signatures = objectSigner.signObject(canonicalJson) signatures = objectSigner.signObject(canonicalJson)
) )
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null) val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null)
uploadKeysTask.execute(uploadDeviceKeysParams) uploadKeysTask.execute(uploadDeviceKeysParams)
cryptoStore.setDeviceKeysUploaded(true) cryptoStore.setDeviceKeysUploaded(true)

View file

@ -136,6 +136,24 @@ internal class MXOlmDevice @Inject constructor(
return store.getOlmAccount().maxOneTimeKeys() return store.getOlmAccount().maxOneTimeKeys()
} }
fun getFallbackKey(): MutableMap<String, MutableMap<String, String>>? {
try {
return store.getOlmAccount().fallbackKey()
} catch (e: Exception) {
Timber.e("## getFallbackKey() : failed")
}
return null
}
fun generateFallbackKey() {
try {
store.getOlmAccount().generateFallbackKey()
store.saveOlmAccount()
} catch (e: Exception) {
Timber.e("## generateFallbackKey() : failed")
}
}
/** /**
* Release the instance * Release the instance
*/ */

View file

@ -54,7 +54,7 @@ internal class OneTimeKeysUploader @Inject constructor(
/** /**
* Check if the OTK must be uploaded. * Check if the OTK must be uploaded.
*/ */
suspend fun maybeUploadOneTimeKeys() { suspend fun maybeUploadOneTimeKeys(shouldGenerateFallbackKey: Boolean) {
if (oneTimeKeyCheckInProgress) { if (oneTimeKeyCheckInProgress) {
Timber.v("maybeUploadOneTimeKeys: already in progress") Timber.v("maybeUploadOneTimeKeys: already in progress")
return return
@ -68,6 +68,10 @@ internal class OneTimeKeysUploader @Inject constructor(
lastOneTimeKeyCheck = System.currentTimeMillis() lastOneTimeKeyCheck = System.currentTimeMillis()
oneTimeKeyCheckInProgress = true oneTimeKeyCheckInProgress = true
if (shouldGenerateFallbackKey) {
olmDevice.generateFallbackKey()
}
// We then check how many keys we can store in the Account object. // We then check how many keys we can store in the Account object.
val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys() val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
@ -96,7 +100,7 @@ internal class OneTimeKeysUploader @Inject constructor(
// So we need some kind of engineering compromise to balance all of // So we need some kind of engineering compromise to balance all of
// these factors. // these factors.
tryOrNull("Unable to upload OTK") { tryOrNull("Unable to upload OTK") {
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit, shouldGenerateFallbackKey)
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
} }
} else { } else {
@ -108,7 +112,7 @@ internal class OneTimeKeysUploader @Inject constructor(
private suspend fun fetchOtkCount(): Int? { private suspend fun fetchOtkCount(): Int? {
return tryOrNull("Unable to get OTK count") { return tryOrNull("Unable to get OTK count") {
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null)) val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null))
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
} }
} }
@ -120,19 +124,22 @@ internal class OneTimeKeysUploader @Inject constructor(
* @param keyLimit the limit * @param keyLimit the limit
* @return the number of uploaded keys * @return the number of uploaded keys
*/ */
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int { private suspend fun uploadOTK(keyCount: Int, keyLimit: Int, shouldGenerateFallbackKey: Boolean): Int {
if (keyLimit <= keyCount) { if (keyLimit <= keyCount && !shouldGenerateFallbackKey) {
// If we don't need to generate any more keys then we are done. // If we don't need to generate any more keys then we are done.
return 0 return 0
} }
val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
olmDevice.generateOneTimeKeys(keysThisLoop) olmDevice.generateOneTimeKeys(keysThisLoop)
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
val fallbackKey = if (shouldGenerateFallbackKey) olmDevice.getFallbackKey() else null
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys(), fallbackKey)
olmDevice.markKeysAsPublished() olmDevice.markKeysAsPublished()
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
// Maybe upload other keys // Maybe upload other keys
return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit, false)
} else { } else {
Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519") throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
@ -142,7 +149,7 @@ internal class OneTimeKeysUploader @Inject constructor(
/** /**
* Upload curve25519 one time keys. * Upload curve25519 one time keys.
*/ */
private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?): KeysUploadResponse { private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?, fallbackKey: Map<String, Map<String, String>>?): KeysUploadResponse {
val oneTimeJson = mutableMapOf<String, Any>() val oneTimeJson = mutableMapOf<String, Any>()
val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty() val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
@ -159,9 +166,25 @@ internal class OneTimeKeysUploader @Inject constructor(
oneTimeJson["signed_curve25519:$key_id"] = k oneTimeJson["signed_curve25519:$key_id"] = k
} }
val fallbackJson = mutableMapOf<String, Any>()
val fallbackCurve25519Map = fallbackKey?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
fallbackCurve25519Map.forEach { (key_id, key) ->
val k = mutableMapOf<String, Any>()
k["key"] = key
k["fallback"] = true
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
k["signatures"] = objectSigner.signObject(canonicalJson)
fallbackJson["signed_curve25519:$key_id"] = k
}
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson) val uploadParams = UploadKeysTask.Params(
deviceKeys = null,
oneTimeKeys = oneTimeJson,
fallbackKeys = if (fallbackJson.isNotEmpty()) fallbackJson else null
)
return uploadKeysTask.execute(uploadParams) return uploadKeysTask.execute(uploadParams)
} }

View file

@ -40,5 +40,12 @@ internal data class KeysUploadBody(
* May be absent if no new one-time keys are required. * May be absent if no new one-time keys are required.
*/ */
@Json(name = "one_time_keys") @Json(name = "one_time_keys")
val oneTimeKeys: JsonDict? = null val oneTimeKeys: JsonDict? = null,
/**
* If the user had previously uploaded a fallback key for a given algorithm, it is replaced.
* The server will only keep one fallback key per algorithm for each user.
*/
@Json(name = "org.matrix.msc2732.fallback_keys")
val fallbackKeys: JsonDict? = null
) )

View file

@ -32,7 +32,8 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
// the device keys to send. // the device keys to send.
val deviceKeys: DeviceKeys?, val deviceKeys: DeviceKeys?,
// the one-time keys to send. // the one-time keys to send.
val oneTimeKeys: JsonDict? val oneTimeKeys: JsonDict?,
val fallbackKeys: JsonDict?
) )
} }
@ -44,7 +45,8 @@ internal class DefaultUploadKeysTask @Inject constructor(
override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse { override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
val body = KeysUploadBody( val body = KeysUploadBody(
deviceKeys = params.deviceKeys, deviceKeys = params.deviceKeys,
oneTimeKeys = params.oneTimeKeys oneTimeKeys = params.oneTimeKeys,
fallbackKeys = params.fallbackKeys
) )
Timber.i("## Uploading device keys -> $body") Timber.i("## Uploading device keys -> $body")