Fix / Save account data after update (local echo)

This commit is contained in:
Valere 2020-02-12 18:59:00 +01:00 committed by Valere
parent def01cca8f
commit 64647cb465
6 changed files with 206 additions and 13 deletions

View file

@ -33,6 +33,7 @@ import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.SessionTestParams
import im.vector.matrix.android.common.TestConstants
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.session.sync.model.accountdata.UserAccountDataEvent
import kotlinx.coroutines.Dispatchers
@ -164,9 +165,9 @@ class QuadSTests : InstrumentedTest {
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("Secret should be encrypted with default key", encryptedContent?.get(keyId))
@ -182,12 +183,12 @@ class QuadSTests : InstrumentedTest {
var decryptedSecret: String? = null
val decryptCountDownLatch = CountDownLatch(1)
aliceSession.sharedSecretStorageService.getSecret("secret.of.life" ,
null, //default key
aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
null, //default key
keySpec!!,
object : MatrixCallback<String> {
override fun onFailure(failure: Throwable) {
fail("Fail to decrypt -> " +failure.localizedMessage)
fail("Fail to decrypt -> " + failure.localizedMessage)
decryptCountDownLatch.countDown()
}
@ -202,7 +203,136 @@ class QuadSTests : InstrumentedTest {
Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
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 {
@ -268,4 +398,50 @@ class QuadSTests : InstrumentedTest {
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!!
}
}

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.accountdata
import androidx.lifecycle.LiveData
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.internal.session.sync.model.accountdata.UserAccountDataEvent
@ -31,5 +32,5 @@ interface AccountDataService {
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)
}

View file

@ -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.listeners.ProgressListener
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.EncryptedSecretContent
import im.vector.matrix.android.api.session.securestorage.KeyInfo
@ -82,7 +83,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
accountDataService.updateAccountData(
"$KEY_ID_BASE.$keyId",
signedContent,
signedContent.toContent(),
object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
@ -136,7 +137,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
accountDataService.updateAccountData(
"$KEY_ID_BASE.$keyId",
signedContent,
signedContent.toContent(),
object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
@ -254,7 +255,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
accountDataService.updateAccountData(
type = name,
data = mapOf(
content = mapOf(
"encrypted" to encryptedContents
),
callback = callback

View file

@ -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)
.findFirst()
if (existing != null) {

View file

@ -21,6 +21,7 @@ import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
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.Optional
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.di.MoshiProvider
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.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -37,6 +39,7 @@ internal class DefaultAccountDataService @Inject constructor(
private val monarchy: Monarchy,
@SessionId private val sessionId: String,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val taskExecutor: TaskExecutor
) : 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(
type = type,
any = data
any = content
)) {
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)
}

View file

@ -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.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.UserAccountData
import im.vector.matrix.android.internal.task.Task