mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-18 21:10:02 +03:00
Fix / Save account data after update (local echo)
This commit is contained in:
parent
def01cca8f
commit
64647cb465
6 changed files with 206 additions and 13 deletions
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.common.SessionTestParams
|
import im.vector.matrix.android.common.SessionTestParams
|
||||||
import im.vector.matrix.android.common.TestConstants
|
import im.vector.matrix.android.common.TestConstants
|
||||||
import im.vector.matrix.android.common.TestMatrixCallback
|
import im.vector.matrix.android.common.TestMatrixCallback
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
|
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -164,9 +165,9 @@ class QuadSTests : InstrumentedTest {
|
||||||
TestMatrixCallback(storeCountDownLatch)
|
TestMatrixCallback(storeCountDownLatch)
|
||||||
)
|
)
|
||||||
|
|
||||||
val secretAccountData = assertAccountData(aliceSession,"secret.of.life" )
|
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
|
||||||
|
|
||||||
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*,*>
|
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *>
|
||||||
Assert.assertNotNull("Element should be encrypted", encryptedContent)
|
Assert.assertNotNull("Element should be encrypted", encryptedContent)
|
||||||
Assert.assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
|
Assert.assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
|
||||||
|
|
||||||
|
@ -182,12 +183,12 @@ class QuadSTests : InstrumentedTest {
|
||||||
var decryptedSecret: String? = null
|
var decryptedSecret: String? = null
|
||||||
|
|
||||||
val decryptCountDownLatch = CountDownLatch(1)
|
val decryptCountDownLatch = CountDownLatch(1)
|
||||||
aliceSession.sharedSecretStorageService.getSecret("secret.of.life" ,
|
aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
|
||||||
null, //default key
|
null, //default key
|
||||||
keySpec!!,
|
keySpec!!,
|
||||||
object : MatrixCallback<String> {
|
object : MatrixCallback<String> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
fail("Fail to decrypt -> " +failure.localizedMessage)
|
fail("Fail to decrypt -> " + failure.localizedMessage)
|
||||||
decryptCountDownLatch.countDown()
|
decryptCountDownLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +203,136 @@ class QuadSTests : InstrumentedTest {
|
||||||
|
|
||||||
Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
|
Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
|
||||||
mTestHelper.signout(aliceSession)
|
mTestHelper.signout(aliceSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_SetDefaultLocalEcho() {
|
||||||
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
|
val quadS = aliceSession.sharedSecretStorageService
|
||||||
|
|
||||||
|
val emptyKeySigner = object : KeySigner {
|
||||||
|
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val TEST_KEY_ID = "my.test.Key"
|
||||||
|
|
||||||
|
val countDownLatch = CountDownLatch(1)
|
||||||
|
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner,
|
||||||
|
TestMatrixCallback(countDownLatch))
|
||||||
|
|
||||||
|
mTestHelper.await(countDownLatch)
|
||||||
|
|
||||||
|
//Test that we don't need to wait for an account data sync to access directly the keyid from DB
|
||||||
|
val defaultLatch = CountDownLatch(1)
|
||||||
|
quadS.setDefaultKey(TEST_KEY_ID, TestMatrixCallback(defaultLatch))
|
||||||
|
mTestHelper.await(defaultLatch)
|
||||||
|
|
||||||
|
|
||||||
|
mTestHelper.signout(aliceSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_StoreSecretWithMultipleKey() {
|
||||||
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
val keyId1 = "Key.1"
|
||||||
|
val key1Info = generatedSecret(aliceSession, keyId1, true)
|
||||||
|
val keyId2 = "Key2"
|
||||||
|
val key2Info = generatedSecret(aliceSession, keyId2, true)
|
||||||
|
|
||||||
|
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
|
||||||
|
val storeLatch = CountDownLatch(1)
|
||||||
|
aliceSession.sharedSecretStorageService.storeSecret(
|
||||||
|
"my.secret",
|
||||||
|
mySecretText.toByteArray().toBase64NoPadding(),
|
||||||
|
listOf(keyId1, keyId2),
|
||||||
|
TestMatrixCallback(storeLatch)
|
||||||
|
)
|
||||||
|
mTestHelper.await(storeLatch)
|
||||||
|
|
||||||
|
val accountDataEvent = aliceSession.getAccountData("my.secret")
|
||||||
|
val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
|
||||||
|
|
||||||
|
Assert.assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
|
||||||
|
|
||||||
|
Assert.assertNotNull(encryptedContent?.get(keyId1))
|
||||||
|
Assert.assertNotNull(encryptedContent?.get(keyId2))
|
||||||
|
|
||||||
|
// Assert that can decrypt with both keys
|
||||||
|
val decryptCountDownLatch = CountDownLatch(2)
|
||||||
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
|
keyId1,
|
||||||
|
Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
|
||||||
|
TestMatrixCallback(decryptCountDownLatch)
|
||||||
|
)
|
||||||
|
|
||||||
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
|
keyId2,
|
||||||
|
Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
|
||||||
|
TestMatrixCallback(decryptCountDownLatch)
|
||||||
|
)
|
||||||
|
|
||||||
|
mTestHelper.await(decryptCountDownLatch)
|
||||||
|
|
||||||
|
mTestHelper.signout(aliceSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_GetSecretWithBadPassphrase() {
|
||||||
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
val keyId1 = "Key.1"
|
||||||
|
val passphrase = "The good pass phrase"
|
||||||
|
val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
|
||||||
|
|
||||||
|
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
|
||||||
|
val storeLatch = CountDownLatch(1)
|
||||||
|
aliceSession.sharedSecretStorageService.storeSecret(
|
||||||
|
"my.secret",
|
||||||
|
mySecretText.toByteArray().toBase64NoPadding(),
|
||||||
|
listOf(keyId1),
|
||||||
|
TestMatrixCallback(storeLatch)
|
||||||
|
)
|
||||||
|
mTestHelper.await(storeLatch)
|
||||||
|
|
||||||
|
val decryptCountDownLatch = CountDownLatch(2)
|
||||||
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
|
keyId1,
|
||||||
|
Curve25519AesSha2KeySpec.fromPassphrase(
|
||||||
|
"A bad passphrase",
|
||||||
|
key1Info.content?.passphrase?.salt ?: "",
|
||||||
|
key1Info.content?.passphrase?.iterations ?: 0,
|
||||||
|
null),
|
||||||
|
object : MatrixCallback<String> {
|
||||||
|
override fun onSuccess(data: String) {
|
||||||
|
decryptCountDownLatch.countDown()
|
||||||
|
fail("Should not be able to decrypt")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
Assert.assertTrue(true)
|
||||||
|
decryptCountDownLatch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now try with correct key
|
||||||
|
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||||
|
keyId1,
|
||||||
|
Curve25519AesSha2KeySpec.fromPassphrase(
|
||||||
|
passphrase,
|
||||||
|
key1Info.content?.passphrase?.salt ?: "",
|
||||||
|
key1Info.content?.passphrase?.iterations ?: 0,
|
||||||
|
null),
|
||||||
|
TestMatrixCallback(decryptCountDownLatch)
|
||||||
|
)
|
||||||
|
|
||||||
|
mTestHelper.await(decryptCountDownLatch)
|
||||||
|
|
||||||
|
mTestHelper.signout(aliceSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
|
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
|
||||||
|
@ -268,4 +398,50 @@ class QuadSTests : InstrumentedTest {
|
||||||
|
|
||||||
return creationInfo!!
|
return creationInfo!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
|
||||||
|
|
||||||
|
val quadS = session.sharedSecretStorageService
|
||||||
|
|
||||||
|
val emptyKeySigner = object : KeySigner {
|
||||||
|
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var creationInfo: SSSSKeyCreationInfo? = null
|
||||||
|
|
||||||
|
val generateLatch = CountDownLatch(1)
|
||||||
|
|
||||||
|
quadS.generateKeyWithPassphrase(keyId, keyId,
|
||||||
|
passphrase,
|
||||||
|
emptyKeySigner,
|
||||||
|
null,
|
||||||
|
object : MatrixCallback<SSSSKeyCreationInfo> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: SSSSKeyCreationInfo) {
|
||||||
|
creationInfo = data
|
||||||
|
generateLatch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
Assert.fail("onFailure " + failure.localizedMessage)
|
||||||
|
generateLatch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mTestHelper.await(generateLatch)
|
||||||
|
|
||||||
|
Assert.assertNotNull(creationInfo)
|
||||||
|
|
||||||
|
assertAccountData(session, "m.secret_storage.key.$keyId")
|
||||||
|
if (asDefault) {
|
||||||
|
val setDefaultLatch = CountDownLatch(1)
|
||||||
|
quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
|
||||||
|
mTestHelper.await(setDefaultLatch)
|
||||||
|
assertAccountData(session, DefaultSharedSecureStorage.DEFAULT_KEY_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationInfo!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.accountdata
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
|
|
||||||
|
@ -31,5 +32,5 @@ interface AccountDataService {
|
||||||
|
|
||||||
fun getLiveAccountData(filterType: List<String>): LiveData<List<UserAccountDataEvent>>
|
fun getLiveAccountData(filterType: List<String>): LiveData<List<UserAccountDataEvent>>
|
||||||
|
|
||||||
fun updateAccountData(type: String, data: Any, callback: MatrixCallback<Unit>? = null)
|
fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>? = null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.secrets
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
||||||
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
||||||
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
||||||
|
@ -82,7 +83,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
|
||||||
|
|
||||||
accountDataService.updateAccountData(
|
accountDataService.updateAccountData(
|
||||||
"$KEY_ID_BASE.$keyId",
|
"$KEY_ID_BASE.$keyId",
|
||||||
signedContent,
|
signedContent.toContent(),
|
||||||
object : MatrixCallback<Unit> {
|
object : MatrixCallback<Unit> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
callback.onFailure(failure)
|
callback.onFailure(failure)
|
||||||
|
@ -136,7 +137,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
|
||||||
|
|
||||||
accountDataService.updateAccountData(
|
accountDataService.updateAccountData(
|
||||||
"$KEY_ID_BASE.$keyId",
|
"$KEY_ID_BASE.$keyId",
|
||||||
signedContent,
|
signedContent.toContent(),
|
||||||
object : MatrixCallback<Unit> {
|
object : MatrixCallback<Unit> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
callback.onFailure(failure)
|
callback.onFailure(failure)
|
||||||
|
@ -254,7 +255,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
|
||||||
|
|
||||||
accountDataService.updateAccountData(
|
accountDataService.updateAccountData(
|
||||||
type = name,
|
type = name,
|
||||||
data = mapOf(
|
content = mapOf(
|
||||||
"encrypted" to encryptedContents
|
"encrypted" to encryptedContents
|
||||||
),
|
),
|
||||||
callback = callback
|
callback = callback
|
||||||
|
|
|
@ -210,7 +210,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
|
fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
|
||||||
val existing = realm.where<UserAccountDataEntity>().equalTo(UserAccountDataEntityFields.TYPE, type)
|
val existing = realm.where<UserAccountDataEntity>().equalTo(UserAccountDataEntityFields.TYPE, type)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.Transformations
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
@ -28,6 +29,7 @@ import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
||||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
|
import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
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
|
||||||
|
@ -37,6 +39,7 @@ internal class DefaultAccountDataService @Inject constructor(
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
|
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor
|
||||||
) : AccountDataService {
|
) : AccountDataService {
|
||||||
|
|
||||||
|
@ -87,13 +90,24 @@ internal class DefaultAccountDataService @Inject constructor(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateAccountData(type: String, data: Any, callback: MatrixCallback<Unit>?) {
|
override fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>?) {
|
||||||
updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams(
|
updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams(
|
||||||
type = type,
|
type = type,
|
||||||
any = data
|
any = content
|
||||||
)) {
|
)) {
|
||||||
this.retryCount = 5
|
this.retryCount = 5
|
||||||
callback?.let { this.callback = it }
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
userAccountDataSyncHandler.handleGenericAccountData(realm, type, content)
|
||||||
|
}
|
||||||
|
callback?.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
callback?.onFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
|
Loading…
Add table
Reference in a new issue