From c66e82c4ae1f0c2fa20a6df17050935a0945d807 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 May 2019 12:39:18 +0200 Subject: [PATCH] Create OneTimeKeysManager --- .../android/internal/crypto/CryptoManager.kt | 285 +----------------- .../android/internal/crypto/CryptoModule.kt | 19 +- .../android/internal/crypto/MXOlmDevice.kt | 3 +- .../android/internal/crypto/ObjectSigner.kt | 53 ++++ .../internal/crypto/OneTimeKeysManager.kt | 253 ++++++++++++++++ .../internal/crypto/keysbackup/KeysBackup.kt | 17 +- 6 files changed, 341 insertions(+), 289 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ObjectSigner.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysManager.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index 787e6101..904ff9cd 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -55,7 +55,6 @@ import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.convertToUTF8 -import org.matrix.olm.OlmAccount import org.matrix.olm.OlmManager import timber.log.Timber import java.util.* @@ -84,6 +83,10 @@ internal class CryptoManager( // The key backup service. private val mKeysBackup: KeysBackup, // + private val mObjectSigner: ObjectSigner, + // + private val mOneTimeKeysManager: OneTimeKeysManager, + // private val roomDecryptorProvider: RoomDecryptorProvider, // The SAS verification service. private val mSasVerificationService: DefaultSasVerificationService, @@ -119,8 +122,6 @@ internal class CryptoManager( */ private val myDevice: MXDeviceInfo - private var mLastPublishedOneTimeKeys: Map>? = null - // the encryption is starting private var mIsStarting: Boolean = false @@ -137,8 +138,6 @@ internal class CryptoManager( // the UI thread private val mUIHandler: Handler - private var mOneTimeKeyCount: Int? = null - // TODO //private val mNetworkListener = object : IMXNetworkEventListener { // override fun onNetworkConnectionUpdate(isConnected: Boolean) { @@ -163,12 +162,6 @@ internal class CryptoManager( // Warn the user if some new devices are detected while encrypting a message. private var mWarnOnUnknownDevices = true - // tell if there is a OTK check in progress - private var mOneTimeKeyCheckInProgress = false - - // last OTK check timestamp - private var mLastOneTimeKeyCheck: Long = 0 - // Set of parameters used to configure/customize the end-to-end crypto. private var mCryptoConfig: MXCryptoConfig? = null @@ -421,11 +414,11 @@ internal class CryptoManager( Timber.d(" - device id : " + mCredentials.deviceId) Timber.d(" - ed25519 : " + mOlmDevice.deviceEd25519Key) Timber.d(" - curve25519 : " + mOlmDevice.deviceCurve25519Key) - Timber.d(" - oneTimeKeys: " + mLastPublishedOneTimeKeys) + Timber.d(" - oneTimeKeys: " + mOneTimeKeysManager.mLastPublishedOneTimeKeys) Timber.d("") encryptingThreadHandler.post { - maybeUploadOneTimeKeys(object : MatrixCallback { + mOneTimeKeysManager.maybeUploadOneTimeKeys(object : MatrixCallback { override fun onSuccess(data: Unit) { encryptingThreadHandler.post { // TODO @@ -546,7 +539,7 @@ internal class CryptoManager( if (null != syncResponse.deviceOneTimeKeysCount) { val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0 - updateOneTimeKeyCount(currentCount) + mOneTimeKeysManager.updateOneTimeKeyCount(currentCount) } if (isStarted()) { @@ -555,7 +548,7 @@ internal class CryptoManager( } if (!isCatchingUp && isStarted()) { - maybeUploadOneTimeKeys() + mOneTimeKeysManager.maybeUploadOneTimeKeys() mIncomingRoomKeyRequestManager.processReceivedRoomKeyRequests() } @@ -578,16 +571,6 @@ internal class CryptoManager( } } - /** - * Stores the current one_time_key count which will be handled later (in a call of - * _onSyncCompleted). The count is e.g. coming from a /sync response. - * - * @param currentCount the new count - */ - private fun updateOneTimeKeyCount(currentCount: Int) { - mOneTimeKeyCount = currentCount - } - /** * Find a device by curve25519 identity key * @@ -1264,33 +1247,6 @@ internal class CryptoManager( return res } - /** - * Sign Object - * - * Example: - *
-     *     {
-     *         "[MY_USER_ID]": {
-     *             "ed25519:[MY_DEVICE_ID]": "sign(str)"
-     *         }
-     *     }
-     * 
- * - * @param strToSign the String to sign and to include in the Map - * @return a Map (see example) - */ - override fun signObject(strToSign: String): Map> { - val result = HashMap>() - - val content = HashMap() - - content["ed25519:" + myDevice.deviceId] = mOlmDevice.signMessage(strToSign)!! - - result[myDevice.userId] = content - - return result - } - /** * Handle the 'toDevice' event * @@ -1416,7 +1372,7 @@ internal class CryptoManager( // Sign it val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) - myDevice.signatures = signObject(canonicalJson) + myDevice.signatures = mObjectSigner.signObject(canonicalJson) // For now, we set the device id explicitly, as we may not be using the // same one as used in login. @@ -1426,217 +1382,6 @@ internal class CryptoManager( .executeBy(mTaskExecutor) } - /** - * OTK upload loop - * - * @param keyCount the number of key to generate - * @param keyLimit the limit - * @param callback the asynchronous callback - */ - private fun uploadLoop(keyCount: Int, keyLimit: Int, callback: MatrixCallback) { - if (keyLimit <= keyCount) { - // If we don't need to generate any more keys then we are done. - mUIHandler.post { callback.onSuccess(Unit) } - return - } - - val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) - - mOlmDevice.generateOneTimeKeys(keysThisLoop) - - uploadOneTimeKeys(object : MatrixCallback { - override fun onSuccess(data: KeysUploadResponse) { - encryptingThreadHandler.post { - if (data.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { - uploadLoop(data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit, callback) - } else { - Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") - mUIHandler.post { - callback.onFailure( - Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")) - } - } - } - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - }) - } - - /** - * Check if the OTK must be uploaded. - * - * @param callback the asynchronous callback - */ - private fun maybeUploadOneTimeKeys(callback: MatrixCallback? = null) { - if (mOneTimeKeyCheckInProgress) { - mUIHandler.post { - callback?.onSuccess(Unit) - } - return - } - - if (System.currentTimeMillis() - mLastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { - // we've done a key upload recently. - mUIHandler.post { - callback?.onSuccess(Unit) - } - return - } - - mLastOneTimeKeyCheck = System.currentTimeMillis() - - mOneTimeKeyCheckInProgress = true - - // We then check how many keys we can store in the Account object. - val maxOneTimeKeys = mOlmDevice.getMaxNumberOfOneTimeKeys() - - // Try to keep at most half that number on the server. This leaves the - // rest of the slots free to hold keys that have been claimed from the - // server but we haven't received a message for. - // If we run out of slots when generating new keys then olm will - // discard the oldest private keys first. This will eventually clean - // out stale private keys that won't receive a message. - val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt() - - if (null != mOneTimeKeyCount) { - uploadOTK(mOneTimeKeyCount!!, keyLimit, callback) - } else { - // ask the server how many keys we have - mUploadKeysTask - .configureWith(UploadKeysTask.Params(null, null, myDevice.deviceId)) - .dispatchTo(object : MatrixCallback { - - override fun onSuccess(data: KeysUploadResponse) { - encryptingThreadHandler.post { - if (!hasBeenReleased()) { - // We need to keep a pool of one time public keys on the server so that - // other devices can start conversations with us. But we can only store - // a finite number of private keys in the olm Account object. - // To complicate things further then can be a delay between a device - // claiming a public one time key from the server and it sending us a - // message. We need to keep the corresponding private key locally until - // we receive the message. - // But that message might never arrive leaving us stuck with duff - // private keys clogging up our local storage. - // So we need some kind of enginering compromise to balance all of - // these factors. // TODO Why we do not set mOneTimeKeyCount here? - val keyCount = data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) - uploadOTK(keyCount, keyLimit, callback) - } - } - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## uploadKeys() : failed") - - mOneTimeKeyCount = null - mOneTimeKeyCheckInProgress = false - - mUIHandler.post { - callback?.onFailure(failure) - } - } - }) - .executeBy(mTaskExecutor) - } - } - - /** - * Upload some the OTKs. - * - * @param keyCount the key count - * @param keyLimit the limit - * @param callback the asynchronous callback - */ - private fun uploadOTK(keyCount: Int, keyLimit: Int, callback: MatrixCallback?) { - uploadLoop(keyCount, keyLimit, object : MatrixCallback { - private fun uploadKeysDone(errorMessage: String?) { - if (null != errorMessage) { - Timber.e("## maybeUploadOneTimeKeys() : failed $errorMessage") - } - mOneTimeKeyCount = null - mOneTimeKeyCheckInProgress = false - } - - override fun onSuccess(data: Unit) { - Timber.d("## maybeUploadOneTimeKeys() : succeeded") - uploadKeysDone(null) - - mUIHandler.post { - callback?.onSuccess(Unit) - } - } - - override fun onFailure(failure: Throwable) { - uploadKeysDone(failure.message) - - mUIHandler.post { - callback?.onFailure(failure) - } - } - }) - - } - - /** - * Upload my user's one time keys. - * This method must called on getEncryptingThreadHandler() thread. - * The callback will called on UI thread. - * - * @param callback the asynchronous callback - */ - private fun uploadOneTimeKeys(callback: MatrixCallback?) { - val oneTimeKeys = mOlmDevice.getOneTimeKeys() - val oneTimeJson = HashMap() - - val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY] - - if (null != curve25519Map) { - for (key_id in curve25519Map.keys) { - val k = HashMap() - k["key"] = curve25519Map[key_id]!! - - // the key is also signed - val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, k) - - k["signatures"] = signObject(canonicalJson) - - oneTimeJson["signed_curve25519:$key_id"] = k - } - } - - // For now, we set the device id explicitly, as we may not be using the - // same one as used in login. - mUploadKeysTask - .configureWith(UploadKeysTask.Params(null, oneTimeJson, myDevice.deviceId)) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: KeysUploadResponse) { - encryptingThreadHandler.post { - if (!hasBeenReleased()) { - mLastPublishedOneTimeKeys = oneTimeKeys - mOlmDevice.markKeysAsPublished() - - if (null != callback) { - mUIHandler.post { callback.onSuccess(data) } - } - } - } - } - - override fun onFailure(failure: Throwable) { - if (null != callback) { - mUIHandler.post { callback.onFailure(failure) } - } - - } - }) - .executeBy(mTaskExecutor) - } - - /** * Export the crypto keys * @@ -2068,15 +1813,6 @@ internal class CryptoManager( } companion object { - // max number of keys to upload at once - // Creating keys can be an expensive operation so we limit the - // number we generate in one go to avoid blocking the application - // for too long. - private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5 - - // frequency with which to check & upload one-time keys - private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60 * 1000).toLong() // one minute - /** * Provides the list of unknown devices * @@ -2102,6 +1838,3 @@ internal class CryptoManager( } } } -/** - * Check if the OTK must be uploaded. - */ \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 7dfb47a8..770c728f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -20,14 +20,14 @@ import android.content.Context import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.internal.crypto.api.CryptoApi +import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup +import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.crypto.store.db.hash -import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup -import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.* import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.session.DefaultSession @@ -97,6 +97,16 @@ internal class CryptoModule { MXOlmDevice(get()) } + // ObjectSigner + scope(DefaultSession.SCOPE) { + ObjectSigner(get(), get()) + } + + // OneTimeKeysManager + scope(DefaultSession.SCOPE) { + OneTimeKeysManager(get(), get(), get(), get(), get()) + } + // CryptoManager scope(DefaultSession.SCOPE) { CryptoManager( @@ -112,6 +122,8 @@ internal class CryptoModule { get(), get(), get(), + get(), + get(), // Tasks get(), get(), get(), get(), get(), get(), get(), // Task executor @@ -178,6 +190,7 @@ internal class CryptoModule { // CryptoStore get(), get(), + get(), // Task get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), // Task executor diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt index f8fa39ed..5d04aaca 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt @@ -263,7 +263,8 @@ internal class MXOlmDevice( Timber.d("## createInboundSession() : ciphertext: $ciphertext") try { - Timber.d("## createInboundSession() :ciphertext: SHA256:" + mOlmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8"))) // TODO Extract code from the Log method... + val sha256 = mOlmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8")) + Timber.d("## createInboundSession() :ciphertext: SHA256:" + sha256) } catch (e: Exception) { Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext") } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ObjectSigner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ObjectSigner.kt new file mode 100644 index 00000000..5324a4c5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ObjectSigner.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto + +import im.vector.matrix.android.api.auth.data.Credentials +import java.util.* + +internal class ObjectSigner( + private val mCredentials: Credentials, + private val mOlmDevice: MXOlmDevice) { + + /** + * Sign Object + * + * Example: + *
+     *     {
+     *         "[MY_USER_ID]": {
+     *             "ed25519:[MY_DEVICE_ID]": "sign(str)"
+     *         }
+     *     }
+     * 
+ * + * @param strToSign the String to sign and to include in the Map + * @return a Map (see example) + */ + fun signObject(strToSign: String): Map> { + val result = HashMap>() + + val content = HashMap() + + content["ed25519:" + mCredentials.deviceId] = mOlmDevice.signMessage(strToSign)!! + + result[mCredentials.userId] = content + + return result + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysManager.kt new file mode 100644 index 00000000..fa085edf --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysManager.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.internal.crypto + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.internal.crypto.model.MXKey +import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse +import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask +import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import org.matrix.olm.OlmAccount +import timber.log.Timber +import java.util.* + +internal class OneTimeKeysManager( + private val mCredentials: Credentials, + private val mOlmDevice: MXOlmDevice, + private val mObjectSigner: ObjectSigner, + private val mUploadKeysTask: UploadKeysTask, + private val mTaskExecutor: TaskExecutor +) { + // tell if there is a OTK check in progress + private var mOneTimeKeyCheckInProgress = false + + // last OTK check timestamp + private var mLastOneTimeKeyCheck: Long = 0 + + private var mOneTimeKeyCount: Int? = null + + var mLastPublishedOneTimeKeys: Map>? = null + private set + + /** + * Stores the current one_time_key count which will be handled later (in a call of + * _onSyncCompleted). The count is e.g. coming from a /sync response. + * + * @param currentCount the new count + */ + fun updateOneTimeKeyCount(currentCount: Int) { + mOneTimeKeyCount = currentCount + } + + + /** + * Check if the OTK must be uploaded. + * + * @param callback the asynchronous callback + */ + fun maybeUploadOneTimeKeys(callback: MatrixCallback? = null) { + if (mOneTimeKeyCheckInProgress) { + callback?.onSuccess(Unit) + return + } + + if (System.currentTimeMillis() - mLastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { + // we've done a key upload recently. + callback?.onSuccess(Unit) + return + } + + mLastOneTimeKeyCheck = System.currentTimeMillis() + + mOneTimeKeyCheckInProgress = true + + // We then check how many keys we can store in the Account object. + val maxOneTimeKeys = mOlmDevice.getMaxNumberOfOneTimeKeys() + + // Try to keep at most half that number on the server. This leaves the + // rest of the slots free to hold keys that have been claimed from the + // server but we haven't received a message for. + // If we run out of slots when generating new keys then olm will + // discard the oldest private keys first. This will eventually clean + // out stale private keys that won't receive a message. + val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt() + + if (null != mOneTimeKeyCount) { + uploadOTK(mOneTimeKeyCount!!, keyLimit, callback) + } else { + // ask the server how many keys we have + mUploadKeysTask + .configureWith(UploadKeysTask.Params(null, null, mCredentials.deviceId!!)) + .dispatchTo(object : MatrixCallback { + + override fun onSuccess(data: KeysUploadResponse) { + // We need to keep a pool of one time public keys on the server so that + // other devices can start conversations with us. But we can only store + // a finite number of private keys in the olm Account object. + // To complicate things further then can be a delay between a device + // claiming a public one time key from the server and it sending us a + // message. We need to keep the corresponding private key locally until + // we receive the message. + // But that message might never arrive leaving us stuck with duff + // private keys clogging up our local storage. + // So we need some kind of engineering compromise to balance all of + // these factors. // TODO Why we do not set mOneTimeKeyCount here? + val keyCount = data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) + uploadOTK(keyCount, keyLimit, callback) + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## uploadKeys() : failed") + + mOneTimeKeyCount = null + mOneTimeKeyCheckInProgress = false + + callback?.onFailure(failure) + } + }) + .executeBy(mTaskExecutor) + } + } + + /** + * Upload some the OTKs. + * + * @param keyCount the key count + * @param keyLimit the limit + * @param callback the asynchronous callback + */ + private fun uploadOTK(keyCount: Int, keyLimit: Int, callback: MatrixCallback?) { + uploadLoop(keyCount, keyLimit, object : MatrixCallback { + private fun uploadKeysDone(errorMessage: String?) { + if (null != errorMessage) { + Timber.e("## maybeUploadOneTimeKeys() : failed $errorMessage") + } + mOneTimeKeyCount = null + mOneTimeKeyCheckInProgress = false + } + + override fun onSuccess(data: Unit) { + Timber.d("## maybeUploadOneTimeKeys() : succeeded") + uploadKeysDone(null) + + callback?.onSuccess(Unit) + } + + override fun onFailure(failure: Throwable) { + uploadKeysDone(failure.message) + + callback?.onFailure(failure) + } + }) + + } + + /** + * Upload my user's one time keys. + * This method must called on getEncryptingThreadHandler() thread. + * The callback will called on UI thread. + * + * @param callback the asynchronous callback + */ + private fun uploadOneTimeKeys(callback: MatrixCallback?) { + val oneTimeKeys = mOlmDevice.getOneTimeKeys() + val oneTimeJson = HashMap() + + val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY] + + if (null != curve25519Map) { + for (key_id in curve25519Map.keys) { + val k = HashMap() + k["key"] = curve25519Map.getValue(key_id) + + // the key is also signed + val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, k) + + k["signatures"] = mObjectSigner.signObject(canonicalJson) + + oneTimeJson["signed_curve25519:$key_id"] = k + } + } + + // For now, we set the device id explicitly, as we may not be using the + // same one as used in login. + mUploadKeysTask + .configureWith(UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!)) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: KeysUploadResponse) { + mLastPublishedOneTimeKeys = oneTimeKeys + mOlmDevice.markKeysAsPublished() + + callback?.onSuccess(data) + } + + override fun onFailure(failure: Throwable) { + callback?.onFailure(failure) + } + }) + .executeBy(mTaskExecutor) + } + + /** + * OTK upload loop + * + * @param keyCount the number of key to generate + * @param keyLimit the limit + * @param callback the asynchronous callback + */ + private fun uploadLoop(keyCount: Int, keyLimit: Int, callback: MatrixCallback) { + if (keyLimit <= keyCount) { + // If we don't need to generate any more keys then we are done. + callback.onSuccess(Unit) + return + } + + val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) + + mOlmDevice.generateOneTimeKeys(keysThisLoop) + + uploadOneTimeKeys(object : MatrixCallback { + override fun onSuccess(data: KeysUploadResponse) { + if (data.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { + uploadLoop(data.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit, callback) + } else { + Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") + callback.onFailure( + Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")) + } + } + + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + }) + } + + companion object { + // max number of keys to upload at once + // Creating keys can be an expensive operation so we limit the + // number we generate in one go to avoid blocking the application + // for too long. + private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5 + + // frequency with which to check & upload one-time keys + private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60 * 1000).toLong() // one minute + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index 6fce19bf..b4629df1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -36,14 +36,14 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersi import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.* -import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity -import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult -import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo -import im.vector.matrix.android.internal.crypto.model.MXOlmInboundGroupSession2 import im.vector.matrix.android.internal.crypto.keysbackup.tasks.* import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXOlmInboundGroupSession2 +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.TaskExecutor @@ -66,6 +66,7 @@ internal class KeysBackup( private val mCredentials: Credentials, private val mCryptoStore: IMXCryptoStore, private val mOlmDevice: MXOlmDevice, + private val mObjectSigner: ObjectSigner, // Tasks private val mCreateKeysBackupVersionTask: CreateKeysBackupVersionTask, private val mDeleteBackupTask: DeleteBackupTask, @@ -175,7 +176,7 @@ internal class KeysBackup( val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) - megolmBackupAuthData.signatures = mKeysBackupCryptoListener.signObject(canonicalJson) + megolmBackupAuthData.signatures = mObjectSigner.signObject(canonicalJson) val megolmBackupCreationInfo = MegolmBackupCreationInfo() @@ -502,7 +503,7 @@ internal class KeysBackup( // Add current device signature val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) - val deviceSignatures = mKeysBackupCryptoListener.signObject(canonicalJson) + val deviceSignatures = mObjectSigner.signObject(canonicalJson) deviceSignatures[myUserId]?.forEach { entry -> myUserSignatures[entry.key] = entry.value @@ -1484,8 +1485,6 @@ internal class KeysBackup( } interface KeysBackupCryptoListener { - fun signObject(strToSign: String): Map> - fun importMegolmSessionsData(megolmSessionsData: List, backUpKeys: Boolean, progressListener: ProgressListener?,