forked from GitHub-Mirror/riotX-android
Create OneTimeKeysManager
This commit is contained in:
parent
a2210a6b0d
commit
c66e82c4ae
@ -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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.convertToUTF8
|
import im.vector.matrix.android.internal.util.convertToUTF8
|
||||||
import org.matrix.olm.OlmAccount
|
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -84,6 +83,10 @@ internal class CryptoManager(
|
|||||||
// The key backup service.
|
// The key backup service.
|
||||||
private val mKeysBackup: KeysBackup,
|
private val mKeysBackup: KeysBackup,
|
||||||
//
|
//
|
||||||
|
private val mObjectSigner: ObjectSigner,
|
||||||
|
//
|
||||||
|
private val mOneTimeKeysManager: OneTimeKeysManager,
|
||||||
|
//
|
||||||
private val roomDecryptorProvider: RoomDecryptorProvider,
|
private val roomDecryptorProvider: RoomDecryptorProvider,
|
||||||
// The SAS verification service.
|
// The SAS verification service.
|
||||||
private val mSasVerificationService: DefaultSasVerificationService,
|
private val mSasVerificationService: DefaultSasVerificationService,
|
||||||
@ -119,8 +122,6 @@ internal class CryptoManager(
|
|||||||
*/
|
*/
|
||||||
private val myDevice: MXDeviceInfo
|
private val myDevice: MXDeviceInfo
|
||||||
|
|
||||||
private var mLastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null
|
|
||||||
|
|
||||||
// the encryption is starting
|
// the encryption is starting
|
||||||
private var mIsStarting: Boolean = false
|
private var mIsStarting: Boolean = false
|
||||||
|
|
||||||
@ -137,8 +138,6 @@ internal class CryptoManager(
|
|||||||
// the UI thread
|
// the UI thread
|
||||||
private val mUIHandler: Handler
|
private val mUIHandler: Handler
|
||||||
|
|
||||||
private var mOneTimeKeyCount: Int? = null
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
//private val mNetworkListener = object : IMXNetworkEventListener {
|
//private val mNetworkListener = object : IMXNetworkEventListener {
|
||||||
// override fun onNetworkConnectionUpdate(isConnected: Boolean) {
|
// 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.
|
// Warn the user if some new devices are detected while encrypting a message.
|
||||||
private var mWarnOnUnknownDevices = true
|
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.
|
// Set of parameters used to configure/customize the end-to-end crypto.
|
||||||
private var mCryptoConfig: MXCryptoConfig? = null
|
private var mCryptoConfig: MXCryptoConfig? = null
|
||||||
|
|
||||||
@ -421,11 +414,11 @@ internal class CryptoManager(
|
|||||||
Timber.d(" - device id : " + mCredentials.deviceId)
|
Timber.d(" - device id : " + mCredentials.deviceId)
|
||||||
Timber.d(" - ed25519 : " + mOlmDevice.deviceEd25519Key)
|
Timber.d(" - ed25519 : " + mOlmDevice.deviceEd25519Key)
|
||||||
Timber.d(" - curve25519 : " + mOlmDevice.deviceCurve25519Key)
|
Timber.d(" - curve25519 : " + mOlmDevice.deviceCurve25519Key)
|
||||||
Timber.d(" - oneTimeKeys: " + mLastPublishedOneTimeKeys)
|
Timber.d(" - oneTimeKeys: " + mOneTimeKeysManager.mLastPublishedOneTimeKeys)
|
||||||
Timber.d("")
|
Timber.d("")
|
||||||
|
|
||||||
encryptingThreadHandler.post {
|
encryptingThreadHandler.post {
|
||||||
maybeUploadOneTimeKeys(object : MatrixCallback<Unit> {
|
mOneTimeKeysManager.maybeUploadOneTimeKeys(object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
encryptingThreadHandler.post {
|
encryptingThreadHandler.post {
|
||||||
// TODO
|
// TODO
|
||||||
@ -546,7 +539,7 @@ internal class CryptoManager(
|
|||||||
|
|
||||||
if (null != syncResponse.deviceOneTimeKeysCount) {
|
if (null != syncResponse.deviceOneTimeKeysCount) {
|
||||||
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
||||||
updateOneTimeKeyCount(currentCount)
|
mOneTimeKeysManager.updateOneTimeKeyCount(currentCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStarted()) {
|
if (isStarted()) {
|
||||||
@ -555,7 +548,7 @@ internal class CryptoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isCatchingUp && isStarted()) {
|
if (!isCatchingUp && isStarted()) {
|
||||||
maybeUploadOneTimeKeys()
|
mOneTimeKeysManager.maybeUploadOneTimeKeys()
|
||||||
|
|
||||||
mIncomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
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
|
* Find a device by curve25519 identity key
|
||||||
*
|
*
|
||||||
@ -1264,33 +1247,6 @@ internal class CryptoManager(
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign Object
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* <pre>
|
|
||||||
* {
|
|
||||||
* "[MY_USER_ID]": {
|
|
||||||
* "ed25519:[MY_DEVICE_ID]": "sign(str)"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param strToSign the String to sign and to include in the Map
|
|
||||||
* @return a Map (see example)
|
|
||||||
*/
|
|
||||||
override fun signObject(strToSign: String): Map<String, Map<String, String>> {
|
|
||||||
val result = HashMap<String, Map<String, String>>()
|
|
||||||
|
|
||||||
val content = HashMap<String, String>()
|
|
||||||
|
|
||||||
content["ed25519:" + myDevice.deviceId] = mOlmDevice.signMessage(strToSign)!!
|
|
||||||
|
|
||||||
result[myDevice.userId] = content
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the 'toDevice' event
|
* Handle the 'toDevice' event
|
||||||
*
|
*
|
||||||
@ -1416,7 +1372,7 @@ internal class CryptoManager(
|
|||||||
// Sign it
|
// Sign it
|
||||||
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
|
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
|
// 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.
|
||||||
@ -1426,217 +1382,6 @@ internal class CryptoManager(
|
|||||||
.executeBy(mTaskExecutor)
|
.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<Unit>) {
|
|
||||||
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<KeysUploadResponse> {
|
|
||||||
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<Unit>? = 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<KeysUploadResponse> {
|
|
||||||
|
|
||||||
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<Unit>?) {
|
|
||||||
uploadLoop(keyCount, keyLimit, object : MatrixCallback<Unit> {
|
|
||||||
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<KeysUploadResponse>?) {
|
|
||||||
val oneTimeKeys = mOlmDevice.getOneTimeKeys()
|
|
||||||
val oneTimeJson = HashMap<String, Any>()
|
|
||||||
|
|
||||||
val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY]
|
|
||||||
|
|
||||||
if (null != curve25519Map) {
|
|
||||||
for (key_id in curve25519Map.keys) {
|
|
||||||
val k = HashMap<String, Any>()
|
|
||||||
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<KeysUploadResponse> {
|
|
||||||
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
|
* Export the crypto keys
|
||||||
*
|
*
|
||||||
@ -2068,15 +1813,6 @@ internal class CryptoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
* Provides the list of unknown devices
|
||||||
*
|
*
|
||||||
@ -2102,6 +1838,3 @@ internal class CryptoManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Check if the OTK must be uploaded.
|
|
||||||
*/
|
|
@ -20,14 +20,14 @@ import android.content.Context
|
|||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
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.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.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
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.RealmCryptoStoreMigration
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
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.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.tasks.*
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
||||||
import im.vector.matrix.android.internal.session.DefaultSession
|
import im.vector.matrix.android.internal.session.DefaultSession
|
||||||
@ -97,6 +97,16 @@ internal class CryptoModule {
|
|||||||
MXOlmDevice(get())
|
MXOlmDevice(get())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ObjectSigner
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
ObjectSigner(get(), get())
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneTimeKeysManager
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
OneTimeKeysManager(get(), get(), get(), get(), get())
|
||||||
|
}
|
||||||
|
|
||||||
// CryptoManager
|
// CryptoManager
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
CryptoManager(
|
CryptoManager(
|
||||||
@ -112,6 +122,8 @@ internal class CryptoModule {
|
|||||||
get(),
|
get(),
|
||||||
get(),
|
get(),
|
||||||
get(),
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
// Tasks
|
// Tasks
|
||||||
get(), get(), get(), get(), get(), get(), get(),
|
get(), get(), get(), get(), get(), get(), get(),
|
||||||
// Task executor
|
// Task executor
|
||||||
@ -178,6 +190,7 @@ internal class CryptoModule {
|
|||||||
// CryptoStore
|
// CryptoStore
|
||||||
get(),
|
get(),
|
||||||
get(),
|
get(),
|
||||||
|
get(),
|
||||||
// Task
|
// Task
|
||||||
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
|
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
|
||||||
// Task executor
|
// Task executor
|
||||||
|
@ -263,7 +263,8 @@ internal class MXOlmDevice(
|
|||||||
|
|
||||||
Timber.d("## createInboundSession() : ciphertext: $ciphertext")
|
Timber.d("## createInboundSession() : ciphertext: $ciphertext")
|
||||||
try {
|
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) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
|
Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "[MY_USER_ID]": {
|
||||||
|
* "ed25519:[MY_DEVICE_ID]": "sign(str)"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param strToSign the String to sign and to include in the Map
|
||||||
|
* @return a Map (see example)
|
||||||
|
*/
|
||||||
|
fun signObject(strToSign: String): Map<String, Map<String, String>> {
|
||||||
|
val result = HashMap<String, Map<String, String>>()
|
||||||
|
|
||||||
|
val content = HashMap<String, String>()
|
||||||
|
|
||||||
|
content["ed25519:" + mCredentials.deviceId] = mOlmDevice.signMessage(strToSign)!!
|
||||||
|
|
||||||
|
result[mCredentials.userId] = content
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<String, Map<String, String>>? = 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<Unit>? = 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<KeysUploadResponse> {
|
||||||
|
|
||||||
|
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<Unit>?) {
|
||||||
|
uploadLoop(keyCount, keyLimit, object : MatrixCallback<Unit> {
|
||||||
|
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<KeysUploadResponse>?) {
|
||||||
|
val oneTimeKeys = mOlmDevice.getOneTimeKeys()
|
||||||
|
val oneTimeJson = HashMap<String, Any>()
|
||||||
|
|
||||||
|
val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY]
|
||||||
|
|
||||||
|
if (null != curve25519Map) {
|
||||||
|
for (key_id in curve25519Map.keys) {
|
||||||
|
val k = HashMap<String, Any>()
|
||||||
|
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<KeysUploadResponse> {
|
||||||
|
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<Unit>) {
|
||||||
|
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<KeysUploadResponse> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -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.MegolmBackupAuthData
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
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.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.tasks.*
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
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.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.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
@ -66,6 +66,7 @@ internal class KeysBackup(
|
|||||||
private val mCredentials: Credentials,
|
private val mCredentials: Credentials,
|
||||||
private val mCryptoStore: IMXCryptoStore,
|
private val mCryptoStore: IMXCryptoStore,
|
||||||
private val mOlmDevice: MXOlmDevice,
|
private val mOlmDevice: MXOlmDevice,
|
||||||
|
private val mObjectSigner: ObjectSigner,
|
||||||
// Tasks
|
// Tasks
|
||||||
private val mCreateKeysBackupVersionTask: CreateKeysBackupVersionTask,
|
private val mCreateKeysBackupVersionTask: CreateKeysBackupVersionTask,
|
||||||
private val mDeleteBackupTask: DeleteBackupTask,
|
private val mDeleteBackupTask: DeleteBackupTask,
|
||||||
@ -175,7 +176,7 @@ internal class KeysBackup(
|
|||||||
|
|
||||||
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
|
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
|
||||||
|
|
||||||
megolmBackupAuthData.signatures = mKeysBackupCryptoListener.signObject(canonicalJson)
|
megolmBackupAuthData.signatures = mObjectSigner.signObject(canonicalJson)
|
||||||
|
|
||||||
|
|
||||||
val megolmBackupCreationInfo = MegolmBackupCreationInfo()
|
val megolmBackupCreationInfo = MegolmBackupCreationInfo()
|
||||||
@ -502,7 +503,7 @@ internal class KeysBackup(
|
|||||||
// Add current device signature
|
// Add current device signature
|
||||||
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
|
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
|
||||||
|
|
||||||
val deviceSignatures = mKeysBackupCryptoListener.signObject(canonicalJson)
|
val deviceSignatures = mObjectSigner.signObject(canonicalJson)
|
||||||
|
|
||||||
deviceSignatures[myUserId]?.forEach { entry ->
|
deviceSignatures[myUserId]?.forEach { entry ->
|
||||||
myUserSignatures[entry.key] = entry.value
|
myUserSignatures[entry.key] = entry.value
|
||||||
@ -1484,8 +1485,6 @@ internal class KeysBackup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface KeysBackupCryptoListener {
|
interface KeysBackupCryptoListener {
|
||||||
fun signObject(strToSign: String): Map<String, Map<String, String>>
|
|
||||||
|
|
||||||
fun importMegolmSessionsData(megolmSessionsData: List<MegolmSessionData>,
|
fun importMegolmSessionsData(megolmSessionsData: List<MegolmSessionData>,
|
||||||
backUpKeys: Boolean,
|
backUpKeys: Boolean,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
|
Loading…
Reference in New Issue
Block a user