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/CoreCryptoCentral.kt b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt index d512afc4b62..6550756b165 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt @@ -80,10 +80,10 @@ class CoreCryptoCentralImpl( override suspend fun mlsClient( clientId: CryptoQualifiedClientId, - cipherSuite: Ciphersuites, + allowedCipherSuites: Ciphersuites, defaultCipherSuite: UShort ): MLSClient { - cc.mlsInit(clientId.toString().encodeToByteArray(), cipherSuite, null) + cc.mlsInit(clientId.toString().encodeToByteArray(), allowedCipherSuites, null) return MLSClientImpl(cc, defaultCipherSuite) } 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..1099bd4038b 100644 --- a/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt +++ b/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt @@ -36,6 +36,12 @@ class MLSClientTest : BaseMLSClientTest() { return createMLSClient(user.qualifiedClientId, ALLOWED_CIPHER_SUITES, DEFAULT_CIPHER_SUITES) } + @Test + fun givemMlsClient_whenCallingGetDefaultCipherSuite_ReturnExpectedValue() = runTest { + val mlsClient = createClient(ALICE1) + assertEquals(DEFAULT_CIPHER_SUITES, mlsClient.getDefaultCipherSuite()) + } + @Test fun givenClient_whenCallingGetPublicKey_ReturnNonEmptyResult() = runTest { val mlsClient = createClient(ALICE1) @@ -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()) @@ -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,6 +194,7 @@ class MLSClientTest : BaseMLSClientTest() { } companion object { + val externalSenderKey = ByteArray(32) val ALLOWED_CIPHER_SUITES = listOf(1.toUShort()) val DEFAULT_CIPHER_SUITES = 1.toUShort() const val MLS_CONVERSATION_ID = "JfflcPtUivbg+1U3Iyrzsh5D2ui/OGS5Rvf52ipH5KY=" 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 9660afa8f0f..8dbabc4f59f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt @@ -189,7 +189,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 bf29065fa2f..abb5179a81f 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 @@ -23,7 +23,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 @@ -48,6 +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.mls.CipherSuite import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysMapper import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository import com.wire.kalium.logic.data.user.UserId @@ -333,10 +333,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) + ) + } } } } @@ -571,14 +573,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 + ) + } } } @@ -592,7 +597,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) @@ -602,14 +607,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/client/RegisterMLSClientUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCase.kt index 27043e05357..da1a61a906f 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 @@ -30,6 +30,7 @@ import com.wire.kalium.logic.functional.Either 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.kaliumLogger import com.wire.kalium.logic.wrapMLSRequest sealed class 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 bfbfe2b9451..7ec0caeea79 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 @@ -51,12 +51,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 @@ -165,10 +163,11 @@ class MLSConversationRepositoryTest { @Test fun givenSuccessfulResponses_whenCallingEstablishMLSGroup_thenGroupIsCreatedAndCommitBundleIsSentAndAccepted() = runTest { val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -177,8 +176,8 @@ class MLSConversationRepositoryTest { result.shouldSucceed() verify(arrangement.mlsClient) - .function(arrangement.mlsClient::createConversation) - .with(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY))) + .suspendFunction(arrangement.mlsClient::createConversation) + .with(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.CRYPTO_MLS_PUBLIC_KEY)) .wasInvoked(once) verify(arrangement.mlsClient) @@ -202,10 +201,11 @@ class MLSConversationRepositoryTest { val userMissingKeyPackage = TestUser.USER_ID.copy(value = "missingKP") val usersMissingKeyPackages = setOf(userMissingKeyPackage) val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful(usersWithoutKeyPackages = usersMissingKeyPackages) .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -220,8 +220,8 @@ class MLSConversationRepositoryTest { } verify(arrangement.mlsClient) - .function(arrangement.mlsClient::createConversation) - .with(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY))) + .suspendFunction(arrangement.mlsClient::createConversation) + .with(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.CRYPTO_MLS_PUBLIC_KEY)) .wasInvoked(once) verify(arrangement.mlsClient) @@ -253,10 +253,11 @@ class MLSConversationRepositoryTest { val usersWithKeyPackages = setOf(userWithKeyPackage) val keyPackageSuccess = KEY_PACKAGE.copy(userId = userWithKeyPackage.value, domain = userWithKeyPackage.domain) val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful(keyPackages = listOf(keyPackageSuccess), usersWithoutKeyPackages = usersMissingKeyPackages) .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -273,7 +274,7 @@ class MLSConversationRepositoryTest { verify(arrangement.mlsClient) .function(arrangement.mlsClient::createConversation) - .with(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY))) + .with(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.CRYPTO_MLS_PUBLIC_KEY)) .wasInvoked(once) verify(arrangement.mlsClient) @@ -303,7 +304,7 @@ class MLSConversationRepositoryTest { .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -316,7 +317,7 @@ class MLSConversationRepositoryTest { .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful(COMMIT_BUNDLE.copy(crlNewDistributionPoints = listOf("url"))) .withCheckRevocationListResult() .withSendCommitBundleSuccessful() @@ -338,10 +339,11 @@ class MLSConversationRepositoryTest { @Test fun givenMlsClientMismatchError_whenCallingEstablishMLSGroup_thenClearCommitAndRetry() = runTest { val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleFailing(Arrangement.MLS_CLIENT_MISMATCH_ERROR, times = 1) .withWaitUntilLiveSuccessful() @@ -369,10 +371,11 @@ class MLSConversationRepositoryTest { @Test fun givenMlsStaleMessageError_whenCallingEstablishMLSGroup_thenAbortCommitAndWipeData() = runTest { val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleFailing(Arrangement.MLS_STALE_MESSAGE_ERROR) .arrange() @@ -399,10 +402,11 @@ class MLSConversationRepositoryTest { @Test fun givenSuccessfulResponses_whenCallingEstablishMLSGroup_thenKeyPackagesAreClaimedForMembers() = runTest { val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful() .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withAddMLSMemberSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -419,10 +423,11 @@ class MLSConversationRepositoryTest { @Test fun givenNoOtherClients_whenCallingEstablishMLSGroup_thenCommitIsCreatedByUpdatingKeyMaterial() = runTest { val (arrangement, mlsConversationRepository) = Arrangement() + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) .withCommitPendingProposalsReturningNothing() .withClaimKeyPackagesSuccessful(keyPackages = emptyList()) .withGetMLSClientSuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withUpdateKeyingMaterialSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -1550,7 +1555,7 @@ class MLSConversationRepositoryTest { .withGetMLSClientSuccessful() .withGetMLSGroupIdByConversationIdReturns(Arrangement.GROUP_ID.value) .withGetExternalSenderKeySuccessful() - .withGetPublicKeysSuccessful() + .withKeyForCipherSuite() .withUpdateKeyingMaterialSuccessful() .withSendCommitBundleSuccessful() .arrange() @@ -1559,8 +1564,8 @@ class MLSConversationRepositoryTest { result.shouldSucceed() verify(arrangement.mlsClient) - .function(arrangement.mlsClient::createConversation) - .with(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_EXTERNAL_KEY))) + .suspendFunction(arrangement.mlsClient::createConversation) + .with(eq(Arrangement.RAW_GROUP_ID), eq(Arrangement.EXTERNAL_SENDER_KEY.value)) .wasInvoked(once) verify(arrangement.mlsMessageApi) @@ -1753,11 +1758,11 @@ class MLSConversationRepositoryTest { .thenReturn(result) } - fun withGetPublicKeysSuccessful() = apply { + fun withKeyForCipherSuite() = apply { given(mlsPublicKeysRepository) - .suspendFunction(mlsPublicKeysRepository::getKeys) - .whenInvoked() - .then { Either.Right(listOf(MLS_PUBLIC_KEY)) } + .suspendFunction(mlsPublicKeysRepository::getKeyForCipherSuite) + .whenInvokedWith(any()) + .then { Either.Right(CRYPTO_MLS_PUBLIC_KEY) } } fun withGetMLSClientSuccessful() = apply { @@ -1960,6 +1965,13 @@ class MLSConversationRepositoryTest { .thenReturn(identitiesMap) } + fun withGetDefaultCipherSuite(cipherSuite: CipherSuite) = apply { + given(mlsClient) + .function(mlsClient::getDefaultCipherSuite) + .whenInvoked() + .thenReturn(cipherSuite.tag.toUShort()) + } + companion object { val TEST_FAILURE = Either.Left(CoreFailure.Unknown(Throwable("an error"))) const val EPOCH = 5UL @@ -1970,11 +1982,13 @@ class MLSConversationRepositoryTest { 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", @@ -1984,7 +1998,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 ce9219916e6..3929dce2e0d 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,12 +118,12 @@ data class UpdateClientCapabilitiesRequest( @Serializable enum class MLSPublicKeyTypeDTO { - @SerialName("p256") - P256, - @SerialName("p384") - P384, - @SerialName("p521") - P521, + @SerialName("ecdsa_secp256r1_sha256") + ECDSA_SECP256R1_SHA256, + @SerialName("ecdsa_secp384r1_sha384") + ECDSA_SECP384R1_SHA384, + @SerialName("ecdsa_secp521r1_sha512") + ECDSA_SECP521R1_SHA512, @SerialName("ed448") ED448, @SerialName("ed25519") 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) }