diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt index 0489ee179f..eb4773f3c8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.ssss import androidx.lifecycle.Observer import androidx.test.ext.junit.runners.AndroidJUnit4 import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.KeySigner @@ -31,7 +30,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.common.TestMatrixCallback import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService @@ -40,7 +38,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.amshove.kluent.shouldBe import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -70,8 +67,8 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - mTestHelper.doSync { - quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it) + mTestHelper.runBlockingTest { + quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Assert Account data is updated @@ -99,7 +96,9 @@ class QuadSTests : InstrumentedTest { assertNull("Key was not generated from passphrase", parsed.passphrase) // Set as default key - quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback {}) + GlobalScope.launch { + quadS.setDefaultKey(TEST_KEY_ID) + } var defaultKeyAccountData: UserAccountDataEvent? = null val defaultDataLock = CountDownLatch(1) @@ -133,12 +132,11 @@ class QuadSTests : InstrumentedTest { // Store a secret val clearSecret = "42".toByteArray().toBase64NoPadding() - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "secret.of.life", clearSecret, - listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key - it + listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key ) } @@ -155,12 +153,11 @@ class QuadSTests : InstrumentedTest { // Try to decrypt?? - val decryptedSecret = mTestHelper.doSync { + val decryptedSecret = mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret( "secret.of.life", null, // default key - keySpec!!, - it + keySpec!! ) } @@ -176,13 +173,13 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - mTestHelper.doSync { - quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it) + mTestHelper.runBlockingTest { + quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Test that we don't need to wait for an account data sync to access directly the keyid from DB - mTestHelper.doSync { - quadS.setDefaultKey(TEST_KEY_ID, it) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(TEST_KEY_ID) } mTestHelper.signOutAndClose(aliceSession) @@ -198,15 +195,14 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf( SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)), SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)) - ), - it + ) ) } @@ -219,19 +215,17 @@ class QuadSTests : InstrumentedTest { assertNotNull(encryptedContent?.get(keyId2)) // Assert that can decrypt with both keys - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, - RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!, - it + RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! ) } - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId2, - RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!, - it + RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) } @@ -247,50 +241,34 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), - listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))), - it + listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) ) } - val decryptCountDownLatch = CountDownLatch(1) - var error = false - aliceSession.sharedSecretStorageService.getSecret("my.secret", - keyId1, - RawBytesKeySpec.fromPassphrase( - "A bad passphrase", - key1Info.content?.passphrase?.salt ?: "", - key1Info.content?.passphrase?.iterations ?: 0, - null), - object : MatrixCallback { - override fun onSuccess(data: String) { - decryptCountDownLatch.countDown() - } - - override fun onFailure(failure: Throwable) { - error = true - decryptCountDownLatch.countDown() - } - } - ) - - mTestHelper.await(decryptCountDownLatch) - - error shouldBe true + mTestHelper.runBlockingTest { + aliceSession.sharedSecretStorageService.getSecret("my.secret", + keyId1, + RawBytesKeySpec.fromPassphrase( + "A bad passphrase", + key1Info.content?.passphrase?.salt ?: "", + key1Info.content?.passphrase?.iterations ?: 0, + null) + ) + } // Now try with correct key - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( passphrase, key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, - null), - it + null) ) } @@ -321,15 +299,15 @@ class QuadSTests : InstrumentedTest { private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.doSync { - quadS.generateKey(keyId, null, keyId, emptyKeySigner, it) + val creationInfo = mTestHelper.runBlockingTest { + quadS.generateKey(keyId, null, keyId, emptyKeySigner) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - mTestHelper.doSync { - quadS.setDefaultKey(keyId, it) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(keyId) } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } @@ -340,21 +318,20 @@ class QuadSTests : InstrumentedTest { private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.doSync { + val creationInfo = mTestHelper.runBlockingTest { quadS.generateKeyWithPassphrase( keyId, keyId, passphrase, emptyKeySigner, - null, - it) + null) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - val setDefaultLatch = CountDownLatch(1) - quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch)) - mTestHelper.await(setDefaultLatch) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(keyId) + } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt index f5d2a7df3e..5ebeaad3de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt @@ -17,9 +17,7 @@ package org.matrix.android.sdk.api.session.accountdata import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional interface AccountDataService { @@ -48,5 +46,5 @@ interface AccountDataService { /** * Update the account data with the provided type and the provided account data content */ - fun updateAccountData(type: String, content: Content, callback: MatrixCallback? = null): Cancelable + suspend fun updateAccountData(type: String, content: Content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt index 37ecf99f9a..721a2bc8af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.securestorage -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -43,13 +42,12 @@ interface SharedSecretStorageService { * @param keyName a human readable name * @param keySigner Used to add a signature to the key (client should check key signature before storing secret) * - * @param callback Get key creation info + * @return key creation info */ - fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?, - callback: MatrixCallback) + suspend fun generateKey(keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner?): SsssKeyCreationInfo /** * Generates a SSSS key using the given passphrase. @@ -61,14 +59,13 @@ interface SharedSecretStorageService { * @param keySigner Used to add a signature to the key (client should check key signature before retrieving secret) * @param progressListener The derivation of the passphrase may take long depending on the device, use this to report progress * - * @param callback Get key creation info + * @return key creation info */ - fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?, - callback: MatrixCallback) + suspend fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?): SsssKeyCreationInfo fun getKey(keyId: String): KeyInfoResult @@ -80,7 +77,7 @@ interface SharedSecretStorageService { */ fun getDefaultKey(): KeyInfoResult - fun setDefaultKey(keyId: String, callback: MatrixCallback) + suspend fun setDefaultKey(keyId: String) /** * Check whether we have a key with a given ID. @@ -98,7 +95,7 @@ interface SharedSecretStorageService { * @param secret The secret contents. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. */ - fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) + suspend fun storeSecret(name: String, secretBase64: String, keys: List) /** * Use this call to determine which SSSSKeySpec to use for requesting secret @@ -113,7 +110,7 @@ interface SharedSecretStorageService { * @param secretKey the secret key to use (@see #RawBytesKeySpec) * */ - fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) + suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String /** * Return true if SSSS is configured diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 82b5185fe8..1f80ce2c81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto.secrets -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.AccountDataService @@ -43,10 +42,9 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.olm.OlmPkMessage import java.security.SecureRandom import javax.crypto.Cipher @@ -64,21 +62,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor( private val cryptoCoroutineScope: CoroutineScope ) : SharedSecretStorageService { - override fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val bytes = try { - (key as? RawBytesKeySpec)?.privateKey - ?: ByteArray(32).also { - SecureRandom().nextBytes(it) - } - } catch (failure: Throwable) { - callback.onFailure(failure) - return@launch - } + override suspend fun generateKey(keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner?): SsssKeyCreationInfo { + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + val bytes = (key as? RawBytesKeySpec)?.privateKey + ?: ByteArray(32).also { + SecureRandom().nextBytes(it) + } val storageKeyContent = SecretStorageKeyContent( name = keyName, @@ -92,34 +84,22 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData( - "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - object : MatrixCallback { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - callback.onSuccess(SsssKeyCreationInfo( - keyId = keyId, - content = storageKeyContent, - recoveryKey = computeRecoveryKey(bytes), - keySpec = RawBytesKeySpec(bytes) - )) - } - } + accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) + SsssKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(bytes), + keySpec = RawBytesKeySpec(bytes) ) } } - override fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + override suspend fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?): SsssKeyCreationInfo { + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) val storageKeyContent = SecretStorageKeyContent( @@ -135,21 +115,13 @@ internal class DefaultSharedSecretStorageService @Inject constructor( accountDataService.updateAccountData( "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - object : MatrixCallback { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - callback.onSuccess(SsssKeyCreationInfo( - keyId = keyId, - content = storageKeyContent, - recoveryKey = computeRecoveryKey(privatePart.privateKey), - keySpec = RawBytesKeySpec(privatePart.privateKey) - )) - } - } + signedContent.toContent() + ) + SsssKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(privatePart.privateKey), + keySpec = RawBytesKeySpec(privatePart.privateKey) ) } } @@ -168,15 +140,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } ?: KeyInfoResult.Error(SharedSecretStorageError.UnknownAlgorithm(keyId)) } - override fun setDefaultKey(keyId: String, callback: MatrixCallback) { + override suspend fun setDefaultKey(keyId: String) { val existingKey = getKey(keyId) if (existingKey is KeyInfoResult.Success) { - accountDataService.updateAccountData(DEFAULT_KEY_ID, - mapOf("key" to keyId), - callback - ) + accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) } else { - callback.onFailure(SharedSecretStorageError.UnknownKey(keyId)) + throw SharedSecretStorageError.UnknownKey(keyId) } } @@ -188,42 +157,31 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return getKey(keyId) } - override fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + override suspend fun storeSecret(name: String, secretBase64: String, keys: List) { + withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { val encryptedContents = HashMap() - try { - keys.forEach { - val keyId = it.keyId - // encrypt the content - when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { - is KeyInfoResult.Success -> { - if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { - encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { - encryptedContents[key.keyInfo.id] = it - } - } else { - // Unknown algorithm - callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) - return@launch + keys.forEach { + val keyId = it.keyId + // encrypt the content + when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { + is KeyInfoResult.Success -> { + if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { + encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { + encryptedContents[key.keyInfo.id] = it } - } - is KeyInfoResult.Error -> { - callback.onFailure(key.error) - return@launch + } else { + // Unknown algorithm + throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "") } } + is KeyInfoResult.Error -> throw key.error } - - accountDataService.updateAccountData( - type = name, - content = mapOf( - "encrypted" to encryptedContents - ), - callback = callback - ) - } catch (failure: Throwable) { - callback.onFailure(failure) } + + accountDataService.updateAccountData( + type = name, + content = mapOf("encrypted" to encryptedContents) + ) } } @@ -344,57 +302,40 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return results } - override fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) { - val accountData = accountDataService.getAccountDataEvent(name) ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.UnknownSecret(name)) - } - val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.SecretNotEncrypted(name)) - } - val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.UnknownKey(name)) - } + override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String { + val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) + val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: throw SharedSecretStorageError.SecretNotEncrypted(name) + val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success + ?: throw SharedSecretStorageError.UnknownKey(name) - val encryptedForKey = encryptedContent[key.keyInfo.id] ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id)) - } + val encryptedForKey = encryptedContent[key.keyInfo.id] ?: throw SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id) val secretContent = EncryptedSecretContent.fromJson(encryptedForKey) - ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.ParsingError) - } + ?: throw SharedSecretStorageError.ParsingError val algorithm = key.keyInfo.content if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.BadKeyFormat) - } - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - // decrypt from recovery key - withOlmDecryption { olmPkDecryption -> - olmPkDecryption.setPrivateKey(keySpec.privateKey) - olmPkDecryption.decrypt(OlmPkMessage() - .apply { - mCipherText = secretContent.ciphertext - mEphemeralKey = secretContent.ephemeral - mMac = secretContent.mac - } - ) - } - }.foldToCallback(callback) + val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + // decrypt from recovery key + withOlmDecryption { olmPkDecryption -> + olmPkDecryption.setPrivateKey(keySpec.privateKey) + olmPkDecryption.decrypt(OlmPkMessage() + .apply { + mCipherText = secretContent.ciphertext + mEphemeralKey = secretContent.ephemeral + mMac = secretContent.mac + } + ) + } } } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.BadKeyFormat) - } - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - decryptAesHmacSha2(keySpec, name, secretContent) - }.foldToCallback(callback) + val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + decryptAesHmacSha2(keySpec, name, secretContent) } } else { - callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")) + throw SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt index 1f1e987ebf..27db30f3b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt @@ -18,16 +18,15 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith +import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject internal class DefaultAccountDataService @Inject constructor( @@ -54,26 +53,18 @@ internal class DefaultAccountDataService @Inject constructor( return accountDataDataSource.getLiveAccountDataEvents(types) } - override fun updateAccountData(type: String, content: Content, callback: MatrixCallback?): Cancelable { - return updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams( - type = type, - any = content - )) { - this.retryCount = 5 - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - // TODO Move that to the task (but it created a circular dependencies...) - monarchy.runTransactionSync { realm -> - userAccountDataSyncHandler.handleGenericAccountData(realm, type, content) - } - callback?.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback?.onFailure(failure) - } + override suspend fun updateAccountData(type: String, content: Content) { + val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) + awaitCallback { callback -> + updateUserAccountDataTask.configureWith(params) { + this.retryCount = 5 // TODO: Need to refactor retrying out into a helper method. + this.callback = callback } + .executeBy(taskExecutor) + } + // TODO Move that to the task (but it created a circular dependencies...) + monarchy.runTransactionSync { realm -> + userAccountDataSyncHandler.handleGenericAccountData(realm, type, content) } - .executeBy(taskExecutor) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index e95f250dd3..11a30b304e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -43,7 +43,6 @@ import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.io.ByteArrayOutputStream @@ -220,13 +219,10 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { if (session.getAccountDataEvent(it) != null) { - val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) - } + val res = session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec) decryptedSecretMap[it] = res } else { Timber.w("## Cannot find secret $it in SSSS, skip") @@ -292,13 +288,10 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { if (session.getAccountDataEvent(it) != null) { - val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) - } + val res = session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec) decryptedSecretMap[it] = res } else { Timber.w("## Cannot find secret $it in SSSS, skip") diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt index 8fbef016cf..74bab9b0b6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -97,37 +97,31 @@ class BackupToQuadSMigrationTask @Inject constructor( when { params.passphrase?.isNotEmpty() == true -> { reportProgress(params, R.string.bootstrap_progress_generating_ssss) - awaitCallback { - quadS.generateKeyWithPassphrase( - UUID.randomUUID().toString(), - "ssss_key", - params.passphrase, - EmptyKeySigner(), - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - params.progressListener?.onProgress( - WaitingViewData( - stringProvider.getString( - R.string.bootstrap_progress_generating_ssss_with_info, - "$progress/$total") - )) - } - }, - it - ) - } + quadS.generateKeyWithPassphrase( + UUID.randomUUID().toString(), + "ssss_key", + params.passphrase, + EmptyKeySigner(), + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + params.progressListener?.onProgress( + WaitingViewData( + stringProvider.getString( + R.string.bootstrap_progress_generating_ssss_with_info, + "$progress/$total") + )) + } + } + ) } params.recoveryKey != null -> { reportProgress(params, R.string.bootstrap_progress_generating_ssss_recovery) - awaitCallback { - quadS.generateKey( - UUID.randomUUID().toString(), - extractCurveKeyFromRecoveryKey(params.recoveryKey)?.let { RawBytesKeySpec(it) }, - "ssss_key", - EmptyKeySigner(), - it - ) - } + quadS.generateKey( + UUID.randomUUID().toString(), + extractCurveKeyFromRecoveryKey(params.recoveryKey)?.let { RawBytesKeySpec(it) }, + "ssss_key", + EmptyKeySigner() + ) } else -> { return Result.IllegalParams @@ -137,14 +131,11 @@ class BackupToQuadSMigrationTask @Inject constructor( // Ok, so now we have migrated the old keybackup secret as the quadS key // Now we need to store the keybackup key in SSSS in a compatible way reportProgress(params, R.string.bootstrap_progress_storing_in_sss) - awaitCallback { - quadS.storeSecret( - KEYBACKUP_SECRET_SSSS_NAME, - curveKey.toBase64NoPadding(), - listOf(SharedSecretStorageService.KeyRef(info.keyId, info.keySpec)), - it - ) - } + quadS.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + curveKey.toBase64NoPadding(), + listOf(SharedSecretStorageService.KeyRef(info.keyId, info.keySpec)) + ) // save for gossiping keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index d1a1237463..70cda4bf79 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -126,25 +126,21 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S key with pass: ${params.passphrase != null}") try { - keyInfo = awaitCallback { - params.passphrase?.let { passphrase -> - ssssService.generateKeyWithPassphrase( - UUID.randomUUID().toString(), - "ssss_key", - passphrase, - EmptyKeySigner(), - null, - it - ) - } ?: run { - ssssService.generateKey( - UUID.randomUUID().toString(), - params.keySpec, - "ssss_key", - EmptyKeySigner(), - it - ) - } + keyInfo = params.passphrase?.let { passphrase -> + ssssService.generateKeyWithPassphrase( + UUID.randomUUID().toString(), + "ssss_key", + passphrase, + EmptyKeySigner(), + null + ) + } ?: run { + ssssService.generateKey( + UUID.randomUUID().toString(), + params.keySpec, + "ssss_key", + EmptyKeySigner() + ) } } catch (failure: Failure) { Timber.e("## BootstrapCrossSigningTask: Creating 4S - Failed to generate key <${failure.localizedMessage}>") @@ -159,9 +155,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Set default key") try { - awaitCallback { - ssssService.setDefaultKey(keyInfo.keyId, it) - } + ssssService.setDefaultKey(keyInfo.keyId) } catch (failure: Failure) { // Maybe we could just ignore this error? Timber.e("## BootstrapCrossSigningTask: Creating 4S - Set default key error <${failure.localizedMessage}>") @@ -183,13 +177,11 @@ class BootstrapCrossSigningTask @Inject constructor( ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Storing MSK...") - awaitCallback { - ssssService.storeSecret( - MASTER_KEY_SSSS_NAME, - mskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + ssssService.storeSecret( + MASTER_KEY_SSSS_NAME, + mskPrivateKey, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_save_usk), @@ -197,27 +189,22 @@ class BootstrapCrossSigningTask @Inject constructor( ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Storing USK...") - awaitCallback { - ssssService.storeSecret( - USER_SIGNING_KEY_SSSS_NAME, - uskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), - it - ) - } + ssssService.storeSecret( + USER_SIGNING_KEY_SSSS_NAME, + uskPrivateKey, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_save_ssk), isIndeterminate = true ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Storing SSK...") - awaitCallback { - ssssService.storeSecret( - SELF_SIGNING_KEY_SSSS_NAME, - sskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + ssssService.storeSecret( + SELF_SIGNING_KEY_SSSS_NAME, + sskPrivateKey, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) } catch (failure: Failure) { Timber.e("## BootstrapCrossSigningTask: Creating 4S - Failed to store keys <${failure.localizedMessage}>") // Maybe we could just ignore this error? @@ -265,14 +252,12 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) - awaitCallback { - extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> - ssssService.storeSecret( - KEYBACKUP_SECRET_SSSS_NAME, - secret, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) } } else { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found") @@ -284,14 +269,12 @@ class BootstrapCrossSigningTask @Inject constructor( } if (isValid) { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") - awaitCallback { - extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> - ssssService.storeSecret( - KEYBACKUP_SECRET_SSSS_NAME, - secret, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) } } else { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key is unknown by this session") diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index b2200e6a6d..7880e734a5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -32,7 +32,6 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx data class AccountDataViewState( @@ -58,9 +57,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) { viewModelScope.launch { - awaitCallback { - session.updateAccountData(action.type, emptyMap(), it) - } + session.updateAccountData(action.type, emptyMap()) } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index c05956bea2..f5b2c7e4a8 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -283,11 +283,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo "type" to "m.widget" ) ) - session.updateAccountData( - type = UserAccountDataTypes.TYPE_WIDGETS, - content = addUserWidgetBody, - callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) - ) + launchWidgetAPIAction(widgetPostAPIMediator, eventData) { + session.updateAccountData( + type = UserAccountDataTypes.TYPE_WIDGETS, + content = addUserWidgetBody + ) + } } else { launchWidgetAPIAction(widgetPostAPIMediator, eventData) { session.widgetService().createRoomWidget(