mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Fallback keys implementation.
Author: Onuray - Benoit squashes the 4 commit to cancel the addition on binaries
This commit is contained in:
parent
7cf92ec17d
commit
4ac90f10c1
6 changed files with 84 additions and 15 deletions
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue