diff --git a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt index 248524ed8cc..10939a53fb7 100644 --- a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt +++ b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt @@ -44,6 +44,9 @@ class MLSClientImpl( private val keyRotationDuration: Duration = 30.toDuration(DurationUnit.DAYS) private val defaultGroupConfiguration = CustomConfiguration(keyRotationDuration, MlsWirePolicy.PLAINTEXT) + override fun getDefaultCipherSuite(): UShort { + return defaultCipherSuite + } @Suppress("EmptyFunctionBlock") override suspend fun close() { @@ -97,11 +100,11 @@ class MLSClientImpl( override suspend fun createConversation( groupId: MLSGroupId, - externalSenders: List + externalSenders: ByteArray ) { val conf = ConversationConfiguration( CiphersuiteName.MLS_128_DHKEMX25519_AES128GCM_SHA256_ED25519, - externalSenders.map { toUByteList(it.value) }, + listOf(toUByteList(externalSenders)), defaultGroupConfiguration ) diff --git a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt index 0586fa3a60f..16fd35d87d6 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt @@ -47,6 +47,10 @@ class MLSClientImpl( private val keyRotationDuration: Duration = 30.toDuration(DurationUnit.DAYS) private val defaultGroupConfiguration = CustomConfiguration(keyRotationDuration.toJavaDuration(), MlsWirePolicy.PLAINTEXT) + override fun getDefaultCipherSuite(): UShort { + return defaultCipherSuite + } + override suspend fun close() { coreCrypto.close() } @@ -104,11 +108,12 @@ class MLSClientImpl( override suspend fun createConversation( groupId: MLSGroupId, - externalSenders: List + externalSenders: ByteArray ) { + kaliumLogger.d("createConversation: using defaultCipherSuite=$defaultCipherSuite") val conf = ConversationConfiguration( defaultCipherSuite, - externalSenders.map { it.value }, + listOf(externalSenders), defaultGroupConfiguration ) diff --git a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt index 6698ad01e03..818946514f1 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt @@ -127,11 +127,6 @@ data class DecryptedMessageBundle( } } -@JvmInline -value class Ed22519Key( - val value: ByteArray -) - @JvmInline value class ExternalSenderKey( val value: ByteArray @@ -153,6 +148,11 @@ data class CrlRegistration( @Suppress("TooManyFunctions") interface MLSClient { + /** + * Get the default ciphersuite for the client. + * the Default ciphersuite is set when creating the mls client. + */ + fun getDefaultCipherSuite(): UShort /** * Free up any resources and shutdown the client. @@ -253,7 +253,7 @@ interface MLSClient { */ suspend fun createConversation( groupId: MLSGroupId, - externalSenders: List = emptyList() + externalSenders: ByteArray ) /** diff --git a/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt b/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt index 1c7dbddf330..3c06302039e 100644 --- a/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt +++ b/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt @@ -33,7 +33,13 @@ class MLSClientTest : BaseMLSClientTest() { } private suspend fun createClient(user: SampleUser): MLSClient { - return createMLSClient(user.qualifiedClientId, ALLOWED_CIPHER_SUITES, DEFAULT_CIPHER_SUITES) + return createMLSClient(user.qualifiedClientId, allowedCipherSuites = ALLOWED_CIPHER_SUITES, DEFAULT_CIPHER_SUITES) + } + + @Test + fun givemMlsClient_whenCallingGetDefaultCipherSuite_ReturnExpectedValue() = runTest { + val mlsClient = createClient(ALICE1) + assertEquals(DEFAULT_CIPHER_SUITES, mlsClient.getDefaultCipherSuite()) } @Test @@ -51,7 +57,7 @@ class MLSClientTest : BaseMLSClientTest() { @Test fun givenNewConversation_whenCallingConversationEpoch_ReturnZeroEpoch() = runTest { val mlsClient = createClient(ALICE1) - mlsClient.createConversation(MLS_CONVERSATION_ID) + mlsClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) assertEquals(0UL, mlsClient.conversationEpoch(MLS_CONVERSATION_ID)) } @@ -64,7 +70,7 @@ class MLSClientTest : BaseMLSClientTest() { val aliceKeyPackage = aliceClient.generateKeyPackages(1).first() val clientKeyPackageList = listOf(aliceKeyPackage) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) val welcome = bobClient.addMember(MLS_CONVERSATION_ID, clientKeyPackageList)?.welcome!! bobClient.commitAccepted(MLS_CONVERSATION_ID) val welcomeBundle = aliceClient.processWelcomeMessage(welcome) @@ -82,7 +88,7 @@ class MLSClientTest : BaseMLSClientTest() { val aliceKeyPackage = aliceClient.generateKeyPackages(1).first() val clientKeyPackageList = listOf(aliceKeyPackage) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) val welcome = bobClient.addMember(MLS_CONVERSATION_ID, clientKeyPackageList)!!.welcome!! val welcomeBundle = aliceClient.processWelcomeMessage(welcome) @@ -98,7 +104,7 @@ class MLSClientTest : BaseMLSClientTest() { val alice1KeyPackage = alice1Client.generateKeyPackages(1).first() val clientKeyPackageList = listOf(alice1KeyPackage) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) bobClient.addMember(MLS_CONVERSATION_ID, clientKeyPackageList) bobClient.commitAccepted(MLS_CONVERSATION_ID) val proposal = alice2Client.joinConversation(MLS_CONVERSATION_ID, 1UL) @@ -117,7 +123,7 @@ class MLSClientTest : BaseMLSClientTest() { val clientKeyPackageList = listOf(aliceClient.generateKeyPackages(1).first()) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) val welcome = bobClient.addMember(MLS_CONVERSATION_ID, clientKeyPackageList)?.welcome!! bobClient.commitAccepted(MLS_CONVERSATION_ID) val welcomeBundle = aliceClient.processWelcomeMessage(welcome) @@ -135,7 +141,7 @@ class MLSClientTest : BaseMLSClientTest() { val clientKeyPackageList = listOf(aliceClient.generateKeyPackages(1).first()) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) val welcome = bobClient.addMember(MLS_CONVERSATION_ID, clientKeyPackageList)?.welcome!! bobClient.commitAccepted((MLS_CONVERSATION_ID)) val welcomeBundle = aliceClient.processWelcomeMessage(welcome) @@ -149,7 +155,7 @@ class MLSClientTest : BaseMLSClientTest() { val bobClient = createClient(BOB1) val carolClient = createClient(CAROL1) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) val welcome = bobClient.addMember( MLS_CONVERSATION_ID, listOf(aliceClient.generateKeyPackages(1).first()) @@ -160,7 +166,7 @@ class MLSClientTest : BaseMLSClientTest() { val commit = bobClient.addMember( MLS_CONVERSATION_ID, - listOf( carolClient.generateKeyPackages(1).first()) + listOf(carolClient.generateKeyPackages(1).first()) )?.commit!! assertNull(aliceClient.decryptMessage(MLS_CONVERSATION_ID, commit).first().message) @@ -176,7 +182,7 @@ class MLSClientTest : BaseMLSClientTest() { aliceClient.generateKeyPackages(1).first(), carolClient.generateKeyPackages(1).first() ) - bobClient.createConversation(MLS_CONVERSATION_ID) + bobClient.createConversation(MLS_CONVERSATION_ID, externalSenderKey) val welcome = bobClient.addMember(MLS_CONVERSATION_ID, clientKeyPackageList)?.welcome!! bobClient.commitAccepted(MLS_CONVERSATION_ID) val welcomeBundle = aliceClient.processWelcomeMessage(welcome) @@ -188,8 +194,9 @@ class MLSClientTest : BaseMLSClientTest() { } companion object { - val ALLOWED_CIPHER_SUITES = listOf(1.toUShort()) + val externalSenderKey = ByteArray(32) val DEFAULT_CIPHER_SUITES = 1.toUShort() + val ALLOWED_CIPHER_SUITES = listOf(1.toUShort()) const val MLS_CONVERSATION_ID = "JfflcPtUivbg+1U3Iyrzsh5D2ui/OGS5Rvf52ipH5KY=" const val PLAIN_TEXT = "Hello World" val ALICE1 = SampleUser( diff --git a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt index f2267bbe925..90e782d9c2a 100644 --- a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt +++ b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt @@ -22,6 +22,10 @@ import kotlin.time.Duration @Suppress("TooManyFunctions") class MLSClientImpl : MLSClient { + override fun getDefaultCipherSuite(): UShort { + TODO("Not yet implemented") + } + override suspend fun close() { TODO("Not yet implemented") } @@ -66,7 +70,7 @@ class MLSClientImpl : MLSClient { TODO("Not yet implemented") } - override suspend fun createConversation(groupId: MLSGroupId, externalSenders: List) { + override suspend fun createConversation(groupId: MLSGroupId, externalSenders: ByteArray) { TODO("Not yet implemented") } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt index b4782de6ccf..b9662a8a77c 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt @@ -199,7 +199,7 @@ interface MLSFailure : CoreFailure { data object StaleProposal : MLSFailure data object StaleCommit : MLSFailure - class Generic(internal val exception: Exception) : MLSFailure { + data class Generic(internal val exception: Exception) : MLSFailure { val rootCause: Throwable get() = exception } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt index 4856daf905c..7408105994b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt @@ -24,7 +24,6 @@ import com.wire.kalium.cryptography.CommitBundle import com.wire.kalium.cryptography.CryptoCertificateStatus import com.wire.kalium.cryptography.CryptoQualifiedClientId import com.wire.kalium.cryptography.E2EIClient -import com.wire.kalium.cryptography.Ed22519Key import com.wire.kalium.cryptography.MLSClient import com.wire.kalium.cryptography.WireIdentity import com.wire.kalium.logger.obfuscateId @@ -32,7 +31,6 @@ import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.E2EIFailure import com.wire.kalium.logic.MLSFailure import com.wire.kalium.logic.NetworkFailure -import com.wire.kalium.logic.configuration.server.ServerConfig import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.conversation.mls.MLSAdditionResult @@ -49,7 +47,7 @@ import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.data.keypackage.KeyPackageLimitsProvider import com.wire.kalium.logic.data.keypackage.KeyPackageRepository -import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysMapper +import com.wire.kalium.logic.data.mls.CipherSuite import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.di.MapperProvider @@ -220,10 +218,8 @@ internal class MLSConversationDataSource( private val keyPackageLimitsProvider: KeyPackageLimitsProvider, private val checkRevocationList: CheckRevocationListUseCase, private val certificateRevocationListRepository: CertificateRevocationListRepository, - private val serverConfigLinks: ServerConfig.Links, private val idMapper: IdMapper = MapperProvider.idMapper(), private val conversationMapper: ConversationMapper = MapperProvider.conversationMapper(selfUserId), - private val mlsPublicKeysMapper: MLSPublicKeysMapper = MapperProvider.mlsPublicKeyMapper(), private val mlsCommitBundleMapper: MLSCommitBundleMapper = MapperProvider.mlsCommitBundleMapper(), kaliumDispatcher: KaliumDispatcher = KaliumDispatcherImpl ) : MLSConversationRepository { @@ -312,10 +308,12 @@ internal class MLSConversationDataSource( } sendCommitBundleForExternalCommit(groupID, commitBundle) }.onSuccess { - conversationDAO.updateConversationGroupState( - ConversationEntity.GroupState.ESTABLISHED, - idMapper.toCryptoModel(groupID) - ) + wrapStorageRequest { + conversationDAO.updateConversationGroupState( + ConversationEntity.GroupState.ESTABLISHED, + idMapper.toCryptoModel(groupID) + ) + } } } } @@ -554,14 +552,17 @@ internal class MLSConversationDataSource( members: List, allowSkippingUsersWithoutKeyPackages: Boolean, ): Either = withContext(serialDispatcher) { - mlsPublicKeysRepository.getKeys().flatMap { publicKeys -> - val keys = publicKeys.map { mlsPublicKeysMapper.toCrypto(it) } - establishMLSGroup( - groupID = groupID, - members = members, - keys = keys, - allowPartialMemberList = allowSkippingUsersWithoutKeyPackages - ) + mlsClientProvider.getMLSClient().flatMap { + mlsPublicKeysRepository.getKeyForCipherSuite( + CipherSuite.fromTag(it.getDefaultCipherSuite()) + ).flatMap { key -> + establishMLSGroup( + groupID = groupID, + members = members, + externalSenders = key, + allowPartialMemberList = allowSkippingUsersWithoutKeyPackages + ) + } } } @@ -575,7 +576,7 @@ internal class MLSConversationDataSource( establishMLSGroup( groupID = groupID, members = emptyList(), - keys = listOf(mlsPublicKeysMapper.toCrypto(externalSenderKey)), + externalSenders = externalSenderKey.value, allowPartialMemberList = false ).map { Unit } } ?: Either.Left(StorageFailure.DataNotFound) @@ -585,14 +586,14 @@ internal class MLSConversationDataSource( private suspend fun establishMLSGroup( groupID: GroupID, members: List, - keys: List, + externalSenders: ByteArray, allowPartialMemberList: Boolean = false, ): Either = withContext(serialDispatcher) { mlsClientProvider.getMLSClient().flatMap { mlsClient -> wrapMLSRequest { mlsClient.createConversation( idMapper.toCryptoModel(groupID), - keys + externalSenders ) }.flatMapLeft { if (it is MLSFailure.ConversationAlreadyExists) { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mls/CipherSuite.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mls/CipherSuite.kt index 530e0494020..c90f9e29b67 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mls/CipherSuite.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mls/CipherSuite.kt @@ -102,12 +102,12 @@ sealed class CipherSuite(open val tag: Int) { } fun CipherSuite.signatureAlgorithm(): MLSPublicKeyTypeDTO? = when (this) { - CipherSuite.MLS_128_DHKEMP256_AES128GCM_SHA256_P256 -> MLSPublicKeyTypeDTO.P256 + CipherSuite.MLS_128_DHKEMP256_AES128GCM_SHA256_P256 -> MLSPublicKeyTypeDTO.ECDSA_SECP256R1_SHA256 CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 -> MLSPublicKeyTypeDTO.ED25519 CipherSuite.MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 -> MLSPublicKeyTypeDTO.ED25519 CipherSuite.MLS_128_X25519KYBER768DRAFT00_AES128GCM_SHA256_ED25519 -> MLSPublicKeyTypeDTO.ED25519 - CipherSuite.MLS_256_DHKEMP384_AES256GCM_SHA384_P384 -> MLSPublicKeyTypeDTO.P384 - CipherSuite.MLS_256_DHKEMP521_AES256GCM_SHA512_P521 -> MLSPublicKeyTypeDTO.P521 + CipherSuite.MLS_256_DHKEMP384_AES256GCM_SHA384_P384 -> MLSPublicKeyTypeDTO.ECDSA_SECP384R1_SHA384 + CipherSuite.MLS_256_DHKEMP521_AES256GCM_SHA512_P521 -> MLSPublicKeyTypeDTO.ECDSA_SECP521R1_SHA512 CipherSuite.MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448 -> MLSPublicKeyTypeDTO.ED448 CipherSuite.MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448 -> MLSPublicKeyTypeDTO.ED448 is CipherSuite.UNKNOWN -> null diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKey.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKey.kt deleted file mode 100644 index e66f72e2934..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKey.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.kalium.logic.data.mlspublickeys - -import kotlin.jvm.JvmInline - -@JvmInline -value class Ed25519Key( - val value: ByteArray -) - -data class MLSPublicKey( - val key: Ed25519Key, - val keyType: KeyType -) - -enum class KeyType { REMOVAL } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysMapper.kt index 1da7edbae7c..48d840103c9 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysMapper.kt @@ -18,36 +18,63 @@ package com.wire.kalium.logic.data.mlspublickeys -import com.wire.kalium.cryptography.ExternalSenderKey -import com.wire.kalium.network.api.base.authenticated.serverpublickey.MLSPublicKeysDTO -import io.ktor.util.decodeBase64Bytes +import com.wire.kalium.logic.data.mls.CipherSuite interface MLSPublicKeysMapper { - fun fromDTO(publicKeys: MLSPublicKeysDTO): List - fun toCrypto(publicKey: MLSPublicKey): com.wire.kalium.cryptography.Ed22519Key - fun toCrypto(externalSenderKey: ExternalSenderKey): com.wire.kalium.cryptography.Ed22519Key + fun fromCipherSuite(cipherSuite: CipherSuite): MLSPublicKeyType } class MLSPublicKeysMapperImpl : MLSPublicKeysMapper { - override fun fromDTO(publicKeys: MLSPublicKeysDTO) = with(publicKeys) { - removal?.entries?.mapNotNull { - when (it.key) { - ED25519 -> MLSPublicKey(Ed25519Key(it.value.decodeBase64Bytes()), KeyType.REMOVAL) - else -> null - } - } ?: emptyList() + + override fun fromCipherSuite(cipherSuite: CipherSuite): MLSPublicKeyType { + return when (cipherSuite) { + CipherSuite.MLS_128_DHKEMP256_AES128GCM_SHA256_P256 -> MLSPublicKeyType.ECDSA_SECP256R1_SHA256 + CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 -> MLSPublicKeyType.ED25519 + CipherSuite.MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 -> MLSPublicKeyType.ED25519 + CipherSuite.MLS_128_X25519KYBER768DRAFT00_AES128GCM_SHA256_ED25519 -> MLSPublicKeyType.ED25519 + CipherSuite.MLS_256_DHKEMP384_AES256GCM_SHA384_P384 -> MLSPublicKeyType.ECDSA_SECP384R1_SHA384 + CipherSuite.MLS_256_DHKEMP521_AES256GCM_SHA512_P521 -> MLSPublicKeyType.ECDSA_SECP521R1_SHA512 + CipherSuite.MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448 -> MLSPublicKeyType.ED448 + CipherSuite.MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448 -> MLSPublicKeyType.ED448 + is CipherSuite.UNKNOWN -> MLSPublicKeyType.Unknown(null) + } + } +} + +@Suppress("ClassNaming") +sealed interface MLSPublicKeyType { + val value: String? + + data object ECDSA_SECP256R1_SHA256 : MLSPublicKeyType { + override val value: String = "ecdsa_secp256r1_sha256" } - override fun toCrypto(publicKey: MLSPublicKey) = with(publicKey) { - com.wire.kalium.cryptography.Ed22519Key(key.value) + data object ECDSA_SECP384R1_SHA384 : MLSPublicKeyType { + override val value: String = "ecdsa_secp384r1_sha384" } - override fun toCrypto(externalSenderKey: ExternalSenderKey) = with(externalSenderKey) { - com.wire.kalium.cryptography.Ed22519Key(this.value) + data object ECDSA_SECP521R1_SHA512 : MLSPublicKeyType { + override val value: String = "ecdsa_secp521r1_sha512" } - companion object { - const val ED25519 = "ed25519" + data object ED448 : MLSPublicKeyType { + override val value: String = "ed448" } + data object ED25519 : MLSPublicKeyType { + override val value: String = "ed25519" + } + + data class Unknown(override val value: String?) : MLSPublicKeyType + + companion object { + fun from(value: String) = when (value) { + ECDSA_SECP256R1_SHA256.value -> ECDSA_SECP256R1_SHA256 + ECDSA_SECP384R1_SHA384.value -> ECDSA_SECP384R1_SHA384 + ECDSA_SECP521R1_SHA512.value -> ECDSA_SECP521R1_SHA512 + ED448.value -> ED448 + ED25519.value -> ED25519 + else -> Unknown(value) + } + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt index 82c1ef7bf2a..48709255f9b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt @@ -19,34 +19,55 @@ package com.wire.kalium.logic.data.mlspublickeys import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.MLSFailure +import com.wire.kalium.logic.data.mls.CipherSuite +import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.map +import com.wire.kalium.logic.functional.right import com.wire.kalium.logic.wrapApiRequest import com.wire.kalium.network.api.base.authenticated.serverpublickey.MLSPublicKeyApi +import io.ktor.util.decodeBase64Bytes + +data class MLSPublicKeys( + val removal: Map? +) interface MLSPublicKeysRepository { - suspend fun fetchKeys(): Either> - suspend fun getKeys(): Either> + suspend fun fetchKeys(): Either + suspend fun getKeys(): Either + suspend fun getKeyForCipherSuite(cipherSuite: CipherSuite): Either } class MLSPublicKeysRepositoryImpl( private val mlsPublicKeyApi: MLSPublicKeyApi, - private val mapper: MLSPublicKeysMapper = MLSPublicKeysMapperImpl() + private val mlsPublicKeysMapper: MLSPublicKeysMapper = MapperProvider.mlsPublicKeyMapper() ) : MLSPublicKeysRepository { - var publicKeys: List? = null + // TODO: make it thread safe + var publicKeys: MLSPublicKeys? = null override suspend fun fetchKeys() = wrapApiRequest { mlsPublicKeyApi.getMLSPublicKeys() }.map { - val keys = mapper.fromDTO(it) - publicKeys = keys - keys + MLSPublicKeys(removal = it.removal) } - override suspend fun getKeys(): Either> { + override suspend fun getKeys(): Either { return publicKeys?.let { Either.Right(it) } ?: fetchKeys() } + override suspend fun getKeyForCipherSuite(cipherSuite: CipherSuite): Either { + + return getKeys().flatMap { serverPublicKeys -> + val keySignature = mlsPublicKeysMapper.fromCipherSuite(cipherSuite) + val key = serverPublicKeys.removal?.let { removalKeys -> + removalKeys[keySignature.value] + } ?: return Either.Left(MLSFailure.Generic(IllegalStateException("No key found for cipher suite $cipherSuite"))) + key.decodeBase64Bytes().right() + } + } + } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index 0b51487d3fd..7e48ab6d557 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -664,8 +664,7 @@ class UserSessionScope internal constructor( proposalTimersFlow, keyPackageLimitsProvider, checkRevocationList, - certificateRevocationListRepository, - sessionManager.getServerConfig().links, + certificateRevocationListRepository ) private val e2eiRepository: E2EIRepository diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCase.kt index 27043e05357..b053cef8ea1 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCase.kt @@ -31,6 +31,7 @@ import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.onFailure import com.wire.kalium.logic.functional.right import com.wire.kalium.logic.wrapMLSRequest +import com.wire.kalium.logic.kaliumLogger sealed class RegisterMLSClientResult { data object Success : RegisterMLSClientResult() @@ -53,8 +54,8 @@ internal class RegisterMLSClientUseCaseImpl( private val userConfigRepository: UserConfigRepository ) : RegisterMLSClientUseCase { - override suspend operator fun invoke(clientId: ClientId): Either = - userConfigRepository.getE2EISettings().flatMap { e2eiSettings -> + override suspend operator fun invoke(clientId: ClientId): Either { + return userConfigRepository.getE2EISettings().flatMap { e2eiSettings -> if (e2eiSettings.isRequired && !mlsClientProvider.isMLSClientInitialised()) { return RegisterMLSClientResult.E2EICertificateRequired.right() } else { @@ -71,5 +72,8 @@ internal class RegisterMLSClientUseCaseImpl( }.flatMap { keyPackageRepository.uploadNewKeyPackages(clientId, keyPackageLimitsProvider.refillAmount()) Either.Right(RegisterMLSClientResult.Success) + }.onFailure { + kaliumLogger.e("Failed to register MLS client: $it") } + } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt index d8915d27997..afdce3f788f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt @@ -29,7 +29,6 @@ import com.wire.kalium.cryptography.GroupInfoEncryptionType import com.wire.kalium.cryptography.MLSClient import com.wire.kalium.cryptography.RatchetTreeType import com.wire.kalium.cryptography.RotateBundle -import com.wire.kalium.cryptography.WelcomeBundle import com.wire.kalium.cryptography.WireIdentity import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.E2EIFailure @@ -50,12 +49,10 @@ import com.wire.kalium.logic.data.id.QualifiedClientID import com.wire.kalium.logic.data.id.toCrypto import com.wire.kalium.logic.data.keypackage.KeyPackageLimitsProvider import com.wire.kalium.logic.data.keypackage.KeyPackageRepository -import com.wire.kalium.logic.data.mlspublickeys.Ed25519Key -import com.wire.kalium.logic.data.mlspublickeys.KeyType -import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKey +import com.wire.kalium.logic.data.mls.CipherSuite +import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeys import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository import com.wire.kalium.logic.data.user.UserId -import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCase import com.wire.kalium.logic.framework.TestClient import com.wire.kalium.logic.framework.TestConversation @@ -67,7 +64,6 @@ import com.wire.kalium.logic.test_util.TestKaliumDispatcher import com.wire.kalium.logic.test_util.testKaliumDispatcher import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed -import com.wire.kalium.logic.util.stubs.newServerConfig import com.wire.kalium.network.api.base.authenticated.client.ClientApi import com.wire.kalium.network.api.base.authenticated.client.DeviceTypeDTO import com.wire.kalium.network.api.base.authenticated.client.SimpleClientResponse @@ -161,10 +157,11 @@ class MLSConversationRepositoryTest { @Test fun givenSuccessfulResponses_whenCallingEstablishMLSGroup_thenGroupIsCreatedAndCommitBundleIsSentAndAccepted() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -173,7 +170,7 @@ class MLSConversationRepositoryTest { result.shouldSucceed() coVerify { - arrangement.mlsClient.createConversation(Arrangement.RAW_GROUP_ID, listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY)) + arrangement.mlsClient.createConversation(Arrangement.RAW_GROUP_ID, Arrangement.CRYPTO_MLS_PUBLIC_KEY) }.wasInvoked(once) coVerify { @@ -194,10 +191,11 @@ class MLSConversationRepositoryTest { val userMissingKeyPackage = TestUser.USER_ID.copy(value = "missingKP") val usersMissingKeyPackages = setOf(userMissingKeyPackage) val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful(usersWithoutKeyPackages = usersMissingKeyPackages) .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -212,7 +210,7 @@ class MLSConversationRepositoryTest { } coVerify { - arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY))) + arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.CRYPTO_MLS_PUBLIC_KEY)) }.wasInvoked(once) coVerify { @@ -240,10 +238,11 @@ class MLSConversationRepositoryTest { val usersWithKeyPackages = setOf(userWithKeyPackage) val keyPackageSuccess = KEY_PACKAGE.copy(userId = userWithKeyPackage.value, domain = userWithKeyPackage.domain) val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful(keyPackages = listOf(keyPackageSuccess), usersWithoutKeyPackages = usersMissingKeyPackages) .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -259,7 +258,7 @@ class MLSConversationRepositoryTest { } coVerify { - arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY))) + arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.CRYPTO_MLS_PUBLIC_KEY)) }.wasInvoked(once) coVerify { @@ -285,7 +284,7 @@ class MLSConversationRepositoryTest { .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -298,7 +297,7 @@ class MLSConversationRepositoryTest { .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful(COMMIT_BUNDLE.copy(crlNewDistributionPoints = listOf("url"))) .withCheckRevocationListResult() .withSendCommitBundleSuccessful() @@ -318,10 +317,11 @@ class MLSConversationRepositoryTest { @Test fun givenMlsClientMismatchError_whenCallingEstablishMLSGroup_thenClearCommitAndRetry() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleFailing(Arrangement.MLS_CLIENT_MISMATCH_ERROR, times = 1) .withWaitUntilLiveSuccessful() @@ -346,10 +346,11 @@ class MLSConversationRepositoryTest { @Test fun givenMlsStaleMessageError_whenCallingEstablishMLSGroup_thenAbortCommitAndWipeData() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleFailing(Arrangement.MLS_STALE_MESSAGE_ERROR) .arrange() @@ -373,10 +374,11 @@ class MLSConversationRepositoryTest { @Test fun givenSuccessfulResponses_whenCallingEstablishMLSGroup_thenKeyPackagesAreClaimedForMembers() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -392,10 +394,11 @@ class MLSConversationRepositoryTest { @Test fun givenNoOtherClients_whenCallingEstablishMLSGroup_thenCommitIsCreatedByUpdatingKeyMaterial() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful(keyPackages = emptyList()) .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withUpdateKeyingMaterialSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -1417,7 +1420,7 @@ class MLSConversationRepositoryTest { .withGetMLSClientSuccessful() .withGetMLSGroupIdByConversationIdReturns(Arrangement.GROUP_ID.value) .withGetExternalSenderKeySuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withUpdateKeyingMaterialSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -1426,7 +1429,7 @@ class MLSConversationRepositoryTest { result.shouldSucceed() coVerify { - arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_EXTERNAL_KEY))) + arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.EXTERNAL_SENDER_KEY.value)) }.wasInvoked(once) coVerify { @@ -1547,8 +1550,6 @@ class MLSConversationRepositoryTest { val proposalTimersFlow = MutableSharedFlow() - val serverConfigLink = newServerConfig(1).links - suspend fun arrange() = this to MLSConversationDataSource( TestUser.SELF.id, keyPackageRepository, @@ -1564,7 +1565,6 @@ class MLSConversationRepositoryTest { keyPackageLimitsProvider, checkRevocationList, certificateRevocationListRepository, - serverConfigLink, kaliumDispatcher = kaliumDispatcher ).also { withCommitBundleEventReceiverSucceeding() @@ -1605,7 +1605,13 @@ class MLSConversationRepositoryTest { suspend fun withGetPublicKeysSuccessful() = apply { coEvery { mlsPublicKeysRepository.getKeys() - }.returns(Either.Right(listOf(MLS_PUBLIC_KEY))) + }.returns(Either.Right(MLS_PUBLIC_KEY)) + } + + suspend fun withKeyForCipherSuite() = apply { + coEvery { + mlsPublicKeysRepository.getKeyForCipherSuite(any()) + }.returns(Either.Right(CRYPTO_MLS_PUBLIC_KEY)) } suspend fun withGetMLSClientSuccessful() = apply { @@ -1762,21 +1768,28 @@ class MLSConversationRepositoryTest { }.returns(identitiesMap) } + fun withGetDefaultCipherSuite(cipherSuite: CipherSuite) = apply { + every { + mlsClient.getDefaultCipherSuite() + }.returns(cipherSuite.tag.toUShort()) + } + companion object { val TEST_FAILURE = Either.Left(CoreFailure.Unknown(Throwable("an error"))) const val EPOCH = 5UL const val RAW_GROUP_ID = "groupId" val GROUP_ID = GroupID(RAW_GROUP_ID) - val WELCOME_BUNDLE = WelcomeBundle(RAW_GROUP_ID, null) val TIME = DateTimeUtil.currentIsoDateTimeString() val INVALID_REQUEST_ERROR = KaliumException.InvalidRequestError(ErrorResponse(405, "", "")) val MLS_STALE_MESSAGE_ERROR = KaliumException.InvalidRequestError(ErrorResponse(409, "", "mls-stale-message")) val MLS_CLIENT_MISMATCH_ERROR = KaliumException.InvalidRequestError(ErrorResponse(409, "", "mls-client-mismatch")) - val MLS_PUBLIC_KEY = MLSPublicKey( - Ed25519Key("gRNvFYReriXbzsGu7zXiPtS8kaTvhU1gUJEV9rdFHVw=".decodeBase64Bytes()), - KeyType.REMOVAL + val MLS_PUBLIC_KEY = MLSPublicKeys( + removal = mapOf( + "ed25519" to "gRNvFYReriXbzsGu7zXiPtS8kaTvhU1gUJEV9rdFHVw=" + ) ) - val CRYPTO_MLS_PUBLIC_KEY = MapperProvider.mlsPublicKeyMapper().toCrypto(MLS_PUBLIC_KEY) + + val CRYPTO_MLS_PUBLIC_KEY: ByteArray = MLS_PUBLIC_KEY.removal?.get("ed25519")!!.decodeBase64Bytes() val KEY_PACKAGE = KeyPackageDTO( "client1", "wire.com", @@ -1786,7 +1799,6 @@ class MLSConversationRepositoryTest { ) val WELCOME = "welcome".encodeToByteArray() val EXTERNAL_SENDER_KEY = ExternalSenderKey("externalSenderKey".encodeToByteArray()) - val CRYPTO_MLS_EXTERNAL_KEY = MapperProvider.mlsPublicKeyMapper().toCrypto(EXTERNAL_SENDER_KEY) val COMMIT = "commit".encodeToByteArray() val PUBLIC_GROUP_STATE = "public_group_state".encodeToByteArray() val PUBLIC_GROUP_STATE_BUNDLE = GroupInfoBundle( diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/client/ClientRequest.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/client/ClientRequest.kt index 09a26ef365b..54cb980ac8f 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/client/ClientRequest.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/client/ClientRequest.kt @@ -118,14 +118,14 @@ data class UpdateClientCapabilitiesRequest( @Serializable enum class MLSPublicKeyTypeDTO { - @SerialName("p256") - P256, + @SerialName("ecdsa_secp256r1_sha256") + ECDSA_SECP256R1_SHA256, - @SerialName("p384") - P384, + @SerialName("ecdsa_secp384r1_sha384") + ECDSA_SECP384R1_SHA384, - @SerialName("p521") - P521, + @SerialName("ecdsa_secp521r1_sha512") + ECDSA_SECP521R1_SHA512, @SerialName("ed448") ED448, diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ClientApiV0.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ClientApiV0.kt index 2ac46b3932a..b65532c5886 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ClientApiV0.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ClientApiV0.kt @@ -85,7 +85,7 @@ internal open class ClientApiV0 internal constructor( updateClientMlsPublicKeysRequest: UpdateClientMlsPublicKeysRequest, clientID: String ): NetworkResponse = - wrapKaliumResponse { + wrapKaliumResponse { httpClient.put("$PATH_CLIENTS/$clientID") { setBody(updateClientMlsPublicKeysRequest) }