diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 21b4ffa05c..50f55577d0 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -31,7 +31,7 @@ import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo -import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import io.reactivex.Observable import io.reactivex.Single @@ -123,7 +123,7 @@ class RxSession(private val session: Session) { } } - fun liveAccountData(filter: List): Observable> { + fun liveAccountData(filter: List): Observable> { return session.getLiveAccountData(filter).asObservable() .startWithCallable { session.getAccountData(filter) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt new file mode 100644 index 0000000000..4ea611b875 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2020 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.ssss + +import android.util.Base64 +import androidx.lifecycle.Observer +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +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.KeySigner +import im.vector.matrix.android.api.session.securestorage.SSSSKeyCreationInfo +import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent +import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.common.CommonTestHelper +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.secrets.DefaultSharedSecureStorage +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Assert +import org.junit.Assert.fail +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class QuadSTests : InstrumentedTest { + + private val mTestHelper = CommonTestHelper(context()) + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + @Test + fun test_Generate4SKey() { + + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + + val aliceLatch = CountDownLatch(1) + + val quadS = aliceSession.sharedSecretStorageService + + val emptyKeySigner = object : KeySigner { + override fun sign(canonicalJson: String): Map>? { + return null + } + } + + var recoveryKey: String? = null + + val TEST_KEY_ID = "my.test.Key" + + quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, + object : MatrixCallback { + + override fun onSuccess(data: SSSSKeyCreationInfo) { + recoveryKey = data.recoveryKey + aliceLatch.countDown() + } + + override fun onFailure(failure: Throwable) { + Assert.fail("onFailure " + failure.localizedMessage) + aliceLatch.countDown() + } + }) + + mTestHelper.await(aliceLatch) + + // Assert Account data is updated + val accountDataLock = CountDownLatch(1) + var accountData: UserAccountDataEvent? = null + + val liveAccountData = runBlocking(Dispatchers.Main) { + aliceSession.getLiveAccountData("m.secret_storage.key.$TEST_KEY_ID") + } + val accountDataObserver = Observer?> { t -> + if (t?.getOrNull()?.type == "m.secret_storage.key.$TEST_KEY_ID") { + accountData = t.getOrNull() + accountDataLock.countDown() + } + } + GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) } + + mTestHelper.await(accountDataLock) + + Assert.assertNotNull("Key should be stored in account data", accountData) + val parsed = SecretStorageKeyContent.fromJson(accountData!!.content) + Assert.assertNotNull("Key Content cannot be parsed", parsed) + Assert.assertEquals("Unexpected Algorithm", DefaultSharedSecureStorage.ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm) + Assert.assertEquals("Unexpected key name", "Test Key", parsed.name) + Assert.assertNull("Key was not generated from passphrase", parsed.passphrase) + Assert.assertNotNull("Pubkey should be defined", parsed.publicKey) + + val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(recoveryKey!!) + DefaultSharedSecureStorage.withOlmDecryption { olmPkDecryption -> + val pubKey = olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey) + Assert.assertEquals("Unexpected Public Key", pubKey, parsed.publicKey) + } + + // Set as default key + quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback {}) + + var defaultKeyAccountData: UserAccountDataEvent? = null + val defaultDataLock = CountDownLatch(1) + + val liveDefAccountData = runBlocking(Dispatchers.Main) { + aliceSession.getLiveAccountData(DefaultSharedSecureStorage.DEFAULT_KEY_ID) + } + val accountDefDataObserver = Observer?> { t -> + if (t?.getOrNull()?.type == DefaultSharedSecureStorage.DEFAULT_KEY_ID) { + defaultKeyAccountData = t.getOrNull()!! + defaultDataLock.countDown() + } + } + GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) } + + mTestHelper.await(defaultDataLock) + + + Assert.assertNotNull(defaultKeyAccountData?.content) + Assert.assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key")) + + + mTestHelper.signout(aliceSession) + } + + @Test + fun test_StoreSecret() { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + val keyId = "My.Key" + val info = generatedSecret(aliceSession, keyId, true) + + // Store a secret + + val storeCountDownLatch = CountDownLatch(1) + val clearSecret = Base64.encodeToString("42".toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP) + aliceSession.sharedSecretStorageService.storeSecret( + "secret.of.life", + clearSecret, + null, // default key + TestMatrixCallback(storeCountDownLatch) + ) + + val secretAccountData = assertAccountData(aliceSession,"secret.of.life" ) + + 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)) + + val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId)) + Assert.assertNotNull(secret?.ciphertext) + Assert.assertNotNull(secret?.mac) + Assert.assertNotNull(secret?.ephemeral) + + // Try to decrypt?? + + val keySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(info.recoveryKey) + + var decryptedSecret: String? = null + + val decryptCountDownLatch = CountDownLatch(1) + aliceSession.sharedSecretStorageService.getSecret("secret.of.life" , + null, //default key + keySpec!!, + null, + object : MatrixCallback { + override fun onFailure(failure: Throwable) { + fail("Fail to decrypt -> " +failure.localizedMessage) + decryptCountDownLatch.countDown() + } + + override fun onSuccess(data: String) { + decryptedSecret = data + decryptCountDownLatch.countDown() + } + } + ) + mTestHelper.await(decryptCountDownLatch) + + + Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret) + mTestHelper.signout(aliceSession) + + } + + private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { + val accountDataLock = CountDownLatch(1) + var accountData: UserAccountDataEvent? = null + + val liveAccountData = runBlocking(Dispatchers.Main) { + session.getLiveAccountData(type) + } + val accountDataObserver = Observer?> { t -> + if (t?.getOrNull()?.type == type) { + accountData = t.getOrNull() + accountDataLock.countDown() + } + } + GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) } + mTestHelper.await(accountDataLock) + + Assert.assertNotNull("Account Data type:$type should be found", accountData) + + return accountData!! + } + + private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo { + + val quadS = session.sharedSecretStorageService + + val emptyKeySigner = object : KeySigner { + override fun sign(canonicalJson: String): Map>? { + return null + } + } + + var creationInfo: SSSSKeyCreationInfo? = null + + val generateLatch = CountDownLatch(1) + + quadS.generateKey(keyId, keyId, emptyKeySigner, + object : MatrixCallback { + + 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!! + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 5255d7c224..4167131c68 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.securestorage.SecureStorageService +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.SyncState @@ -161,4 +162,6 @@ interface Session : */ fun onGlobalError(globalError: GlobalError) } + + val sharedSecretStorageService: SharedSecretStorageService } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt index 7a3b9f0171..a832921dc7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt @@ -19,17 +19,17 @@ 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.util.Optional -import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent interface AccountDataService { - fun getAccountData(type: String): UserAccountData? + fun getAccountData(type: String): UserAccountDataEvent? - fun getLiveAccountData(type: String): LiveData> + fun getLiveAccountData(type: String): LiveData> - fun getAccountData(filterType: List): List + fun getAccountData(filterType: List): List - fun getLiveAccountData(filterType: List): LiveData> + fun getLiveAccountData(filterType: List): LiveData> fun updateAccountData(type: String, data: Any, callback: MatrixCallback? = null) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt new file mode 100644 index 0000000000..4c8b51c668 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.di.MoshiProvider + +/** + * The account_data will have an encrypted property that is a map from key ID to an object. + * The algorithm from the m.secret_storage.key.[key ID] data for the given key defines how the other properties are interpreted, + * though it's expected that most encryption schemes would have ciphertext and mac properties, + * where the ciphertext property is the unpadded base64-encoded ciphertext, and the mac is used to ensure the integrity of the data. + */ +@JsonClass(generateAdapter = true) +data class EncryptedSecretContent( + /** unpadded base64-encoded ciphertext */ + @Json(name = "ciphertext") val ciphertext: String? = null, + @Json(name = "mac") val mac: String? = null, + @Json(name = "ephemeral") val ephemeral: String? = null +) { + companion object { + /** + * Facility method to convert from object which must be comprised of maps, lists, + * strings, numbers, booleans and nulls. + */ + fun fromJson(obj: Any?): EncryptedSecretContent? { + return MoshiProvider.providesMoshi() + .adapter(EncryptedSecretContent::class.java) + .fromJsonValue(obj) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt new file mode 100644 index 0000000000..940f5298ef --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +sealed class KeyInfoResult { + data class Success(val keyInfo: KeyInfo) : KeyInfoResult() + data class Error(val error: SharedSecretStorageError) : KeyInfoResult() + + fun isSuccess(): Boolean = this is Success +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt new file mode 100644 index 0000000000..2cd7a74f31 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +interface KeySigner { + fun sign(canonicalJson: String): Map>? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt new file mode 100644 index 0000000000..3c629290fc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +data class SSSSKeyCreationInfo ( + val keyId: String = "", + var content: SecretStorageKeyContent?, + val recoveryKey: String = "" +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt new file mode 100644 index 0000000000..dcdba38d8e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.internal.crypto.keysbackup.deriveKey +import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey + +/** Tag class */ +interface SSSSKeySpec + +data class Curve25519AesSha2KeySpec( + val privateKey: ByteArray +) : SSSSKeySpec { + + companion object { + + fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): Curve25519AesSha2KeySpec { + return Curve25519AesSha2KeySpec( + privateKey = deriveKey( + passphrase, + salt, + iterations, + progressListener + ) + ) + } + + fun fromRecoveryKey(recoveryKey: String): Curve25519AesSha2KeySpec? { + return extractCurveKeyFromRecoveryKey(recoveryKey)?.let { + Curve25519AesSha2KeySpec( + privateKey = it + ) + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Curve25519AesSha2KeySpec + + if (!privateKey.contentEquals(other.privateKey)) return false + + return true + } + + override fun hashCode(): Int { + return privateKey.contentHashCode() + } +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt new file mode 100644 index 0000000000..0aba3d700d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.util.JsonCanonicalizer + +/** + * + * The contents of the account data for the key will include an algorithm property, which indicates the encryption algorithm used, as well as a name property, + * which is a human-readable name. + * The contents will be signed as signed JSON using the user's master cross-signing key. Other properties depend on the encryption algorithm. + * + * + * "content": { + * "algorithm": "m.secret_storage.v1.curve25519-aes-sha2", + * "passphrase": { + * "algorithm": "m.pbkdf2", + * "iterations": 500000, + * "salt": "IrswcMWnYieBALCAOMBw9k93xSzlc2su" + * }, + * "pubkey": "qql1q3IvBbwMU97zLnyh9HYW5x/zqTy5eoK1n+9fm1Y", + * "signatures": { + * "@valere35:matrix.org": { + * "ed25519:nOUQYiH9L8uKp5JajqiQyv+Loa3+lsdil7UBverz/Ko": "QtePmwfUL7+SHYRJT/HaTgF7gUFog1E/wtUCt0qc5aB8N+Sz5iCOvQ0KtaFHQ5SJzsBlYH8k7ejoBc0RcnU7BA" + * } + * } + * } + */ + +data class KeyInfo( + val id: String, + val content: SecretStorageKeyContent +) + +@JsonClass(generateAdapter = true) +data class SecretStorageKeyContent( + /** Currently support m.secret_storage.v1.curve25519-aes-sha2 */ + @Json(name = "algorithm") val algorithm: String? = null, + @Json(name = "name") val name: String? = null, + @Json(name = "passphrase") val passphrase: SSSSPassphrase? = null, + @Json(name = "pubkey") val publicKey: String? = null, + @Json(name = "signatures") + var signatures: Map>? = null +) { + + private fun signalableJSONDictionary(): Map { + val map = HashMap() + algorithm?.let { map["algorithm"] = it } + name?.let { map["name"] = it } + publicKey?.let { map["pubkey"] = it } + passphrase?.let { ssspp -> + map["passphrase"] = mapOf( + "algorithm" to ssspp.algorithm, + "iterations" to ssspp.salt, + "salt" to ssspp.salt + ) + } + return map + } + + fun canonicalSignable(): String { + return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) + } + + companion object { + /** + * Facility method to convert from object which must be comprised of maps, lists, + * strings, numbers, booleans and nulls. + */ + fun fromJson(obj: Any?): SecretStorageKeyContent? { + return MoshiProvider.providesMoshi() + .adapter(SecretStorageKeyContent::class.java) + .fromJsonValue(obj) + } + } +} + +@JsonClass(generateAdapter = true) +data class SSSSPassphrase( + @Json(name = "algorithm") val algorithm: String?, + @Json(name = "iterations") val iterations: Int, + @Json(name = "salt") val salt: String? +) + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt new file mode 100644 index 0000000000..1ff5cf12f3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 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.api.session.securestorage + +sealed class SharedSecretStorageError(message: String?) : Throwable(message) { + + data class UnknownSecret(val secretName: String) : SharedSecretStorageError("Unknown Secret $secretName") + data class UnknownKey(val keyId: String) : SharedSecretStorageError("Unknown key $keyId") + data class UnknownAlgorithm(val keyId: String) : SharedSecretStorageError("Unknown algorithm $keyId") + data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm") + data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName") + data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) : SharedSecretStorageError("Missing content for secret $secretName with key $keyId") + object BadKeyFormat : SharedSecretStorageError("Bad Key Format") + object ParsingError : SharedSecretStorageError("parsing Error") + data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt index fa2042e506..9923aab606 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.securestorage import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.listeners.ProgressListener /** * Some features may require clients to store encrypted data on the server so that it can be shared securely between clients. @@ -39,7 +40,29 @@ interface SharedSecretStorageService { * * @return {string} the ID of the key */ - fun addKey(algorithm: String, opts: Map, keyId: String, callback: MatrixCallback) + fun generateKey(keyId: String, + keyName: String, + keySigner: KeySigner, + callback: MatrixCallback) + + fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?, + callback: MatrixCallback) + + fun getKey(keyId: String): KeyInfoResult + + /** + * A key can be marked as the "default" key by setting the user's account_data with event type m.secret_storage.default_key + * to an object that has the ID of the key as its key property. + * The default key will be used to encrypt all secrets that the user would expect to be available on all their clients. + * Unless the user specifies otherwise, clients will try to use the default key to decrypt secrets. + */ + fun getDefaultKey(): KeyInfoResult + + fun setDefaultKey(keyId: String, callback: MatrixCallback) /** * Check whether we have a key with a given ID. @@ -51,23 +74,29 @@ interface SharedSecretStorageService { /** * Store an encrypted secret on the server + * Clients MUST ensure that the key is trusted before using it to encrypt secrets. * * @param name The name of the secret * @param secret The secret contents. - * @param keys The IDs of the keys to use to encrypt the secret or null to use the default key. + * @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) + /** + * Use this call to determine which SSSSKeySpec to use for requesting secret + */ + fun getAlgorithmsForSecret(name: String): List /** * Get an encrypted secret from the shared storage * * @param name The name of the secret - * @param keyId The id of the key that should be used to decrypt + * @param keyId The id of the key that should be used to decrypt (null for default key) * @param privateKey the passphrase/secret * * @return The decrypted value */ - fun getSecret(name: String, keyId: String, privateKey: String) : String + @Throws + fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index 7906005046..595b55a7a6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -40,8 +40,28 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersi import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.* -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.* +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt index 344ba61277..2429c1e658 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt @@ -83,10 +83,10 @@ fun retrievePrivateKeyWithPassword(password: String, * @return a private key. */ @WorkerThread -private fun deriveKey(password: String, - salt: String, - iterations: Int, - progressListener: ProgressListener?): ByteArray { +fun deriveKey(password: String, + salt: String, + iterations: Int, + progressListener: ProgressListener?): ByteArray { // Note: copied and adapted from MXMegolmExportEncryption val t0 = System.currentTimeMillis() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt new file mode 100644 index 0000000000..a4cf02aeae --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2020 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.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.securestorage.Curve25519AesSha2KeySpec +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.KeyInfoResult +import im.vector.matrix.android.api.session.securestorage.KeySigner +import im.vector.matrix.android.api.session.securestorage.SSSSKeyCreationInfo +import im.vector.matrix.android.api.session.securestorage.SSSSKeySpec +import im.vector.matrix.android.api.session.securestorage.SSSSPassphrase +import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService +import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword +import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey +import im.vector.matrix.android.internal.extensions.foldToCallback +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.matrix.olm.OlmPkDecryption +import org.matrix.olm.OlmPkEncryption +import org.matrix.olm.OlmPkMessage +import javax.inject.Inject + +internal class DefaultSharedSecureStorage @Inject constructor( + private val accountDataService: AccountDataService, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoCoroutineScope: CoroutineScope +) : SharedSecretStorageService { + + override fun generateKey(keyId: String, + keyName: String, + keySigner: KeySigner, + callback: MatrixCallback) { + + cryptoCoroutineScope.launch(coroutineDispatchers.main) { + val pkDecryption = OlmPkDecryption() + val pubKey: String + val privateKey: ByteArray + try { + pubKey = pkDecryption.generateKey() + privateKey = pkDecryption.privateKey() + } catch (failure: Throwable) { + return@launch Unit.also { + callback.onFailure(failure) + } + } finally { + pkDecryption.releaseDecryption() + } + + val storageKeyContent = SecretStorageKeyContent( + name = keyName, + algorithm = ALGORITHM_CURVE25519_AES_SHA2, + passphrase = null, + publicKey = pubKey + ) + + val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let { + storageKeyContent.copy( + signatures = it + ) + } ?: storageKeyContent + + accountDataService.updateAccountData( + "$KEY_ID_BASE.$keyId", + signedContent, + object : MatrixCallback { + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + + override fun onSuccess(data: Unit) { + callback.onSuccess(SSSSKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(privateKey) + )) + } + } + ) + } + } + + override fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?, + callback: MatrixCallback) { + cryptoCoroutineScope.launch(coroutineDispatchers.main) { + + val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) + + val pkDecryption = OlmPkDecryption() + val pubKey: String + try { + pubKey = pkDecryption.setPrivateKey(privatePart.privateKey) + } catch (failure: Throwable) { + return@launch Unit.also { + callback.onFailure(failure) + } + } finally { + pkDecryption.releaseDecryption() + } + + val storageKeyContent = SecretStorageKeyContent( + algorithm = ALGORITHM_CURVE25519_AES_SHA2, + passphrase = SSSSPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt), + publicKey = pubKey + ) + + val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let { + storageKeyContent.copy( + signatures = it + ) + } ?: storageKeyContent + + accountDataService.updateAccountData( + "$KEY_ID_BASE.$keyId", + signedContent, + 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) + )) + } + } + ) + + } + } + + override fun hasKey(keyId: String): Boolean { + return accountDataService.getAccountData("$KEY_ID_BASE.$keyId") != null + } + + override fun getKey(keyId: String): KeyInfoResult { + val accountData = accountDataService.getAccountData("$KEY_ID_BASE.$keyId") + ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(keyId)) + return SecretStorageKeyContent.fromJson(accountData.content)?.let { + KeyInfoResult.Success( + KeyInfo(id = keyId, content = it) + ) + } ?: KeyInfoResult.Error(SharedSecretStorageError.UnknownAlgorithm(keyId)) + } + + override fun setDefaultKey(keyId: String, callback: MatrixCallback) { + val existingKey = getKey(keyId) + if (existingKey is KeyInfoResult.Success) { + accountDataService.updateAccountData(DEFAULT_KEY_ID, + mapOf("key" to keyId), + callback + ) + } else { + callback.onFailure(SharedSecretStorageError.UnknownKey(keyId)) + } + } + + override fun getDefaultKey(): KeyInfoResult { + val accountData = accountDataService.getAccountData(DEFAULT_KEY_ID) + ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID)) + val keyId = accountData.content["key"] as? String + ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID)) + return getKey(keyId) + } + + override fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) { + + cryptoCoroutineScope.launch(coroutineDispatchers.main) { + val encryptedContents = HashMap() + try { + + if (keys == null || keys.isEmpty()) { + //use default key + val key = getDefaultKey() + when (key) { + is KeyInfoResult.Success -> { + if (key.keyInfo.content.algorithm == ALGORITHM_CURVE25519_AES_SHA2) { + withOlmEncryption { olmEncrypt -> + olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey) + val encryptedResult = olmEncrypt.encrypt(secretBase64) + encryptedContents[key.keyInfo.id] = EncryptedSecretContent( + ciphertext = encryptedResult.mCipherText, + ephemeral = encryptedResult.mEphemeralKey, + mac = encryptedResult.mMac + ) + } + } else { + // Unknown algorithm + callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) + return@launch + } + } + is KeyInfoResult.Error -> { + callback.onFailure(key.error) + return@launch + } + } + } else { + keys.forEach { + val keyId = it + // encrypt the content + val key = getKey(keyId) + when (key) { + is KeyInfoResult.Success -> { + if (key.keyInfo.content.algorithm == ALGORITHM_CURVE25519_AES_SHA2) { + withOlmEncryption { olmEncrypt -> + olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey) + val encryptedResult = olmEncrypt.encrypt(secretBase64) + encryptedContents[keyId] = EncryptedSecretContent( + ciphertext = encryptedResult.mCipherText, + ephemeral = encryptedResult.mEphemeralKey, + mac = encryptedResult.mMac + ) + } + } else { + // Unknown algorithm + callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) + return@launch + } + } + is KeyInfoResult.Error -> { + callback.onFailure(key.error) + return@launch + } + } + } + } + + accountDataService.updateAccountData( + type = name, + data = mapOf( + "encrypted" to encryptedContents + ), + callback = callback + ) + } catch (failure: Throwable) { + callback.onFailure(failure) + } + + } + + // Add default key + } + + override fun getAlgorithmsForSecret(name: String): List { + val accountData = accountDataService.getAccountData(name) + ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.UnknownSecret(name))) + val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> + ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.SecretNotEncrypted(name))) + + val results = ArrayList() + encryptedContent.keys.forEach { + (it as? String)?.let { keyId -> + results.add(getKey(keyId)) + } + } + return results + } + + override fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback) { + val accountData = accountDataService.getAccountData(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)) + } + + val encryptedForKey = encryptedContent[key.keyInfo.id] ?: return Unit.also { + callback.onFailure(SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id)) + } + + val secretContent = EncryptedSecretContent.fromJson(encryptedForKey) + ?: return Unit.also { + callback.onFailure(SharedSecretStorageError.ParsingError) + } + + val algorithm = key.keyInfo.content + if (ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { + val keySpec = secretKey as? Curve25519AesSha2KeySpec ?: return Unit.also { + callback.onFailure(SharedSecretStorageError.BadKeyFormat) + } + cryptoCoroutineScope.launch(coroutineDispatchers.main) { + kotlin.runCatching { + // decryt from recovery key + val keyBytes = keySpec.privateKey + val decryption = OlmPkDecryption() + try { + decryption.setPrivateKey(keyBytes) + decryption.decrypt(OlmPkMessage().apply { + mCipherText = secretContent.ciphertext + mEphemeralKey = secretContent.ephemeral + mMac = secretContent.mac + }) + } catch (failure: Throwable) { + throw failure + } finally { + decryption.releaseDecryption() + } + }.foldToCallback(callback) + } + } else { + callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")) + } + } + + companion object { + const val KEY_ID_BASE = "m.secret_storage.key" + const val ENCRYPTED = "encrypted" + const val DEFAULT_KEY_ID = "m.secret_storage.default_key" + + const val ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2" + + fun withOlmEncryption(block: (OlmPkEncryption) -> Unit) { + val olmPkEncryption = OlmPkEncryption() + try { + block(olmPkEncryption) + } catch (failure: Throwable) { + throw failure + } finally { + olmPkEncryption.releaseEncryption() + } + } + + fun withOlmDecryption(block: (OlmPkDecryption) -> Unit) { + val olmPkDecryption = OlmPkDecryption() + try { + block(olmPkDecryption) + } catch (failure: Throwable) { + throw failure + } finally { + olmPkDecryption.releaseDecryption() + } + } + } +} + + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt deleted file mode 100644 index 33306104eb..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020 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.secrets - -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService - -internal class DefaultSharedSecureStorage : SharedSecretStorageService { - - override fun addKey(algorithm: String, opts: Map, keyId: String, callback: MatrixCallback) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun hasKey(keyId: String): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getSecret(name: String, keyId: String, privateKey: String): String { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index c19c686329..cd4e9abbc1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages -import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules @@ -31,7 +31,7 @@ object MoshiProvider { private val moshi: Moshi = Moshi.Builder() .add(UriMoshiAdapter()) - .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) + .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST) .registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index afe37c1c41..46264ceb85 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -38,6 +38,7 @@ import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.securestorage.SecureStorageService +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.SyncState @@ -93,6 +94,7 @@ internal class DefaultSession @Inject constructor( private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy, private val accountDataService: Lazy, + private val _sharedSecretStorageService: Lazy, private val shieldTrustUpdater: ShieldTrustUpdater) : Session, RoomService by roomService.get(), @@ -111,6 +113,9 @@ internal class DefaultSession @Inject constructor( ProfileService by profileService.get(), AccountDataService by accountDataService.get() { + override val sharedSecretStorageService: SharedSecretStorageService + get() = _sharedSecretStorageService.get() + private var isOpen = false private var syncThread: SyncThread? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 636e61c93f..02d8a35009 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -35,6 +35,8 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory @@ -268,4 +270,7 @@ internal abstract class SessionModule { @Binds abstract fun bindAccountDataServiceService(accountDataService: DefaultAccountDataService): AccountDataService + + @Binds + abstract fun bindSharedSecuredSecretStorageService(service: DefaultSharedSecureStorage): SharedSecretStorageService } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataEvent.kt similarity index 96% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataEvent.kt index d965d2ffee..a4ba0fc91a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataEvent.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class UserAccountDataFallback( +data class UserAccountDataEvent( @Json(name = "type") override val type: String, @Json(name = "content") val content: Map ) : UserAccountData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt index 2aeef3cc0d..0bb57f0dae 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt @@ -28,8 +28,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.model.accountdata.UserAccountData -import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback +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 import javax.inject.Inject @@ -44,17 +43,17 @@ internal class DefaultAccountDataService @Inject constructor( private val moshi = MoshiProvider.providesMoshi() private val adapter = moshi.adapter>(JSON_DICT_PARAMETERIZED_TYPE) - override fun getAccountData(type: String): UserAccountData? { + override fun getAccountData(type: String): UserAccountDataEvent? { return getAccountData(listOf(type)).firstOrNull() } - override fun getLiveAccountData(type: String): LiveData> { + override fun getLiveAccountData(type: String): LiveData> { return Transformations.map(getLiveAccountData(listOf(type))) { it.firstOrNull()?.toOptional() } } - override fun getAccountData(filterType: List): List { + override fun getAccountData(filterType: List): List { return monarchy.fetchAllCopiedSync { realm -> realm.where(UserAccountDataEntity::class.java) .apply { @@ -64,7 +63,7 @@ internal class DefaultAccountDataService @Inject constructor( } }?.mapNotNull { entity -> entity.type?.let { type -> - UserAccountDataFallback( + UserAccountDataEvent( type = type, content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() ) @@ -72,7 +71,7 @@ internal class DefaultAccountDataService @Inject constructor( } ?: emptyList() } - override fun getLiveAccountData(filterType: List): LiveData> { + override fun getLiveAccountData(filterType: List): LiveData> { return monarchy.findAllMappedWithChanges({ realm -> realm.where(UserAccountDataEntity::class.java) .apply { @@ -81,7 +80,7 @@ internal class DefaultAccountDataService @Inject constructor( } } }, { entity -> - UserAccountDataFallback( + UserAccountDataEvent( type = entity.type ?: "", content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() ) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt index 5b7b090dcd..a7d5d82fb1 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData -import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import im.vector.riotx.R import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.platform.VectorBaseActivity @@ -55,9 +55,9 @@ class AccountDataFragment @Inject constructor( } override fun didTap(data: UserAccountData) { - val fb = data as? UserAccountDataFallback ?: return + val fb = data as? UserAccountDataEvent ?: return val jsonString = MoshiProvider.providesMoshi() - .adapter(UserAccountDataFallback::class.java) + .adapter(UserAccountDataEvent::class.java) .toJson(fb) JsonViewerBottomSheetDialog.newInstance(jsonString) .show(childFragmentManager, "JSON_VIEWER")