diff --git a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/CoreCryptoCentralImpl.kt b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/CoreCryptoCentralImpl.kt index 259c4e54dc5..3516966da67 100644 --- a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/CoreCryptoCentralImpl.kt +++ b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/CoreCryptoCentralImpl.kt @@ -34,7 +34,7 @@ actual suspend fun coreCryptoCentral( NSFileManager.defaultManager.createDirectoryAtPath(rootDir, withIntermediateDirectories = true, null, null) val coreCrypto = CoreCrypto.deferredInit(path, databaseKey, null) coreCrypto.setCallbacks(Callbacks()) - return CoreCryptoCentralImpl(coreCrypto, rootDir) + return CoreCryptoCentralImpl(coreCrypto, rootDir, defaultCipherSuite) } private class Callbacks : CoreCryptoCallbacks { @@ -59,11 +59,15 @@ private class Callbacks : CoreCryptoCallbacks { } } -class CoreCryptoCentralImpl(private val cc: CoreCrypto, private val rootDir: String) : CoreCryptoCentral { +class CoreCryptoCentralImpl( + private val cc: CoreCrypto, + private val rootDir: String, + private val defaultCipherSuite: UShort? +) : CoreCryptoCentral { override suspend fun mlsClient(clientId: CryptoQualifiedClientId): MLSClient { cc.mlsInit(MLSClientImpl.toUByteList(clientId.toString())) - return MLSClientImpl(cc) + return MLSClientImpl(cc, defaultCipherSuite = defaultCipherSuite!!) } override suspend fun mlsClient( 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 bd7e5ed3f46..248524ed8cc 100644 --- a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt +++ b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt @@ -38,7 +38,8 @@ import kotlin.time.toDuration @Suppress("TooManyFunctions") @OptIn(ExperimentalUnsignedTypes::class) class MLSClientImpl( - private val coreCrypto: CoreCrypto + private val coreCrypto: CoreCrypto, + private val defaultCipherSuite: UShort ) : MLSClient { private val keyRotationDuration: Duration = 30.toDuration(DurationUnit.DAYS) @@ -48,8 +49,8 @@ class MLSClientImpl( override suspend fun close() { } - override suspend fun getPublicKey(): ByteArray { - return coreCrypto.clientPublicKey().toUByteArray().asByteArray() + override suspend fun getPublicKey(): Pair { + return coreCrypto.clientPublicKey().toUByteArray().asByteArray() to defaultCipherSuite } override suspend fun generateKeyPackages(amount: Int): List { 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 0bfa6b1dada..0586fa3a60f 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt @@ -51,8 +51,8 @@ class MLSClientImpl( coreCrypto.close() } - override suspend fun getPublicKey(): ByteArray { - return coreCrypto.clientPublicKey(defaultCipherSuite, toCredentialType(getMLSCredentials())) + override suspend fun getPublicKey(): Pair { + return coreCrypto.clientPublicKey(defaultCipherSuite, toCredentialType(getMLSCredentials())) to defaultCipherSuite } override suspend fun generateKeyPackages(amount: Int): List { 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 72e172f8e29..6698ad01e03 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt @@ -165,8 +165,9 @@ interface MLSClient { * Public key of the client's identity. * * @return public key of the client + * @return ciphersuite used for the public key */ - suspend fun getPublicKey(): ByteArray + suspend fun getPublicKey(): Pair /** * Generate a fresh set of key packages. 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 861007a7609..1c7dbddf330 100644 --- a/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt +++ b/cryptography/src/commonTest/kotlin/com/wire/kalium/cryptography/MLSClientTest.kt @@ -39,7 +39,7 @@ class MLSClientTest : BaseMLSClientTest() { @Test fun givenClient_whenCallingGetPublicKey_ReturnNonEmptyResult() = runTest { val mlsClient = createClient(ALICE1) - assertTrue(mlsClient.getPublicKey().isNotEmpty()) + assertTrue(mlsClient.getPublicKey().first.isNotEmpty()) } @Test @@ -209,5 +209,4 @@ class MLSClientTest : BaseMLSClientTest() { "Carol" ) } - } 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 e275a01350c..f2267bbe925 100644 --- a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt +++ b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt @@ -26,7 +26,7 @@ class MLSClientImpl : MLSClient { TODO("Not yet implemented") } - override suspend fun getPublicKey(): ByteArray { + override suspend fun getPublicKey(): Pair { TODO("Not yet implemented") } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt index 17ba54523e0..47fae3cef3e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt @@ -28,6 +28,7 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel +import com.wire.kalium.logic.data.mls.CipherSuite import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.functional.Either @@ -52,7 +53,12 @@ import kotlinx.coroutines.flow.map @Suppress("TooManyFunctions") interface ClientRepository { suspend fun registerClient(param: RegisterClientParam): Either - suspend fun registerMLSClient(clientId: ClientId, publicKey: ByteArray): Either + suspend fun registerMLSClient( + clientId: ClientId, + publicKey: ByteArray, + cipherSuite: CipherSuite + ): Either + suspend fun hasRegisteredMLSClient(): Either suspend fun persistClientId(clientId: ClientId): Either @@ -204,8 +210,12 @@ class ClientDataSource( .map { it?.let { clientMapper.fromClientEntity(it) } } .wrapStorageRequest() - override suspend fun registerMLSClient(clientId: ClientId, publicKey: ByteArray): Either = - clientRemoteRepository.registerMLSClient(clientId, publicKey.encodeBase64()) + override suspend fun registerMLSClient( + clientId: ClientId, + publicKey: ByteArray, + cipherSuite: CipherSuite + ): Either = + clientRemoteRepository.registerMLSClient(clientId, publicKey.encodeBase64(), cipherSuite) .flatMap { wrapStorageRequest { clientRegistrationStorage.setHasRegisteredMLSClient() diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/remote/ClientRemoteRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/remote/ClientRemoteRepository.kt index f22e32d3d59..0aa9a688928 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/remote/ClientRemoteRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/remote/ClientRemoteRepository.kt @@ -26,28 +26,37 @@ import com.wire.kalium.logic.data.client.DeleteClientParam import com.wire.kalium.logic.data.client.RegisterClientParam import com.wire.kalium.logic.data.client.UpdateClientCapabilitiesParam import com.wire.kalium.logic.data.conversation.ClientId -import com.wire.kalium.logic.data.id.IdMapper +import com.wire.kalium.logic.data.id.toApi +import com.wire.kalium.logic.data.mls.CipherSuite +import com.wire.kalium.logic.data.mls.signatureAlgorithm import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.functional.left import com.wire.kalium.logic.functional.map import com.wire.kalium.logic.wrapApiRequest import com.wire.kalium.network.api.base.authenticated.client.ClientApi -import com.wire.kalium.network.api.base.authenticated.client.MLSPublicKeyTypeDTO import com.wire.kalium.network.api.base.authenticated.client.SimpleClientResponse import com.wire.kalium.network.api.base.authenticated.client.UpdateClientMlsPublicKeysRequest import com.wire.kalium.network.api.base.model.PushTokenBody +import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.api.base.model.UserId as UserIdDTO interface ClientRemoteRepository { suspend fun registerClient(param: RegisterClientParam): Either - suspend fun registerMLSClient(clientId: ClientId, publicKey: String): Either + suspend fun registerMLSClient( + clientId: ClientId, + publicKey: String, + cipherSuite: CipherSuite + ): Either + suspend fun deleteClient(param: DeleteClientParam): Either suspend fun registerToken(body: PushTokenBody): Either suspend fun deregisterToken(pid: String): Either suspend fun fetchOtherUserClients( userIdList: List ): Either>> + suspend fun updateClientCapabilities( updateClientCapabilitiesParam: UpdateClientCapabilitiesParam, clientID: String @@ -58,20 +67,26 @@ class ClientRemoteDataSource( private val clientApi: ClientApi, private val clientConfig: ClientConfig, private val clientMapper: ClientMapper = MapperProvider.clientMapper(), - private val idMapper: IdMapper = MapperProvider.idMapper() ) : ClientRemoteRepository { override suspend fun registerClient(param: RegisterClientParam): Either = wrapApiRequest { clientApi.registerClient(clientMapper.toRegisterClientRequest(clientConfig, param)) } .map { clientResponse -> clientMapper.fromClientDto(clientResponse) } - override suspend fun registerMLSClient(clientId: ClientId, publicKey: String): Either = + override suspend fun registerMLSClient( + clientId: ClientId, + publicKey: String, + cipherSuite: CipherSuite + ): Either = cipherSuite.signatureAlgorithm()?.let { signatureAlgorithm -> wrapApiRequest { clientApi.updateClientMlsPublicKeys( - UpdateClientMlsPublicKeysRequest(mapOf(Pair(MLSPublicKeyTypeDTO.ED25519, publicKey))), + UpdateClientMlsPublicKeysRequest( + mapOf(signatureAlgorithm to publicKey), + ), clientId.value ) } + } ?: NetworkFailure.ServerMiscommunication(KaliumException.GenericError(IllegalArgumentException("Unknown cipher suite"))).left() override suspend fun deleteClient(param: DeleteClientParam): Either = wrapApiRequest { clientApi.deleteClient(param.password, param.clientId.value) } @@ -87,7 +102,7 @@ class ClientRemoteDataSource( override suspend fun fetchOtherUserClients( userIdList: List ): Either>> { - val networkUserId = userIdList.map { idMapper.toNetworkUserId(it) } + val networkUserId = userIdList.map { it.toApi() } return wrapApiRequest { clientApi.listClientsOfUsers(networkUserId) } } 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 161a24b4f09..530e0494020 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 @@ -17,6 +17,8 @@ */ package com.wire.kalium.logic.data.mls +import com.wire.kalium.network.api.base.authenticated.client.MLSPublicKeyTypeDTO + data class SupportedCipherSuite( val supported: List, val default: CipherSuite @@ -94,5 +96,19 @@ sealed class CipherSuite(open val tag: Int) { 61489 -> MLS_128_X25519KYBER768DRAFT00_AES128GCM_SHA256_ED25519 else -> UNKNOWN(tag) } + + fun fromTag(tag: UShort) = fromTag(tag.toInt()) } } + +fun CipherSuite.signatureAlgorithm(): MLSPublicKeyTypeDTO? = when (this) { + CipherSuite.MLS_128_DHKEMP256_AES128GCM_SHA256_P256 -> MLSPublicKeyTypeDTO.P256 + 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_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/feature/client/RegisterMLSClientUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCase.kt index fddf36cb836..27043e05357 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 @@ -25,10 +25,12 @@ import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.conversation.ClientId 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.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.wrapMLSRequest sealed class RegisterMLSClientResult { data object Success : RegisterMLSClientResult() @@ -60,8 +62,12 @@ internal class RegisterMLSClientUseCaseImpl( } }.onFailure { mlsClientProvider.getMLSClient(clientId) - }.flatMap { - clientRepository.registerMLSClient(clientId, it.getPublicKey()) + }.flatMap { mlsClient -> + wrapMLSRequest { + mlsClient.getPublicKey() + } + }.flatMap { (publicKey, cipherSuite) -> + clientRepository.registerMLSClient(clientId, publicKey, CipherSuite.fromTag(cipherSuite)) }.flatMap { keyPackageRepository.uploadNewKeyPackages(clientId, keyPackageLimitsProvider.refillAmount()) Either.Right(RegisterMLSClientResult.Success) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt index 816209ffbf0..c4ac11ab231 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt @@ -28,6 +28,7 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.PlainId import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel +import com.wire.kalium.logic.data.mls.CipherSuite import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.framework.TestClient @@ -85,7 +86,7 @@ class ClientRepositoryTest { .withRegisterMLSClient(Either.Right(Unit)) .arrange() - clientRepository.registerMLSClient(CLIENT_ID, MLS_PUBLIC_KEY) + clientRepository.registerMLSClient(CLIENT_ID, MLS_PUBLIC_KEY, MLS_CIPHER_SUITE) verify(arrangement.clientRemoteRepository) .suspendFunction(arrangement.clientRemoteRepository::registerMLSClient) @@ -477,6 +478,7 @@ class ClientRepositoryTest { secondFactorVerificationCode = SECOND_FACTOR_CODE, ) val MLS_PUBLIC_KEY = "public_key".encodeToByteArray() + val MLS_CIPHER_SUITE = CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 val CLIENT_ID = TestClient.CLIENT_ID val CLIENT_RESULT = TestClient.CLIENT val TEST_FAILURE = NetworkFailure.ServerMiscommunication( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt index c752928e718..d06c1b8eab0 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt @@ -55,7 +55,7 @@ class RegisterMLSClientUseCaseTest { .withIsMLSClientInitialisedReturns() .withMLSClientE2EIIsEnabledReturns(e2eiIsEnrolled) .withGettingE2EISettingsReturns(Either.Right(E2EI_TEAM_SETTINGS.copy(isRequired = e2eiIsRequired))) - .withGetPublicKey(Arrangement.MLS_PUBLIC_KEY) + .withGetPublicKey(Arrangement.MLS_PUBLIC_KEY, Arrangement.MLS_CIPHER_SUITE) .withRegisterMLSClient(Either.Right(Unit)) .withKeyPackageLimits(Arrangement.REFILL_AMOUNT) .withUploadKeyPackagesSuccessful() @@ -88,7 +88,7 @@ class RegisterMLSClientUseCaseTest { .withIsMLSClientInitialisedReturns(false) .withMLSClientE2EIIsEnabledReturns(e2eiIsEnrolled) .withGettingE2EISettingsReturns(Either.Right(E2EI_TEAM_SETTINGS.copy(isRequired = e2eiIsRequired))) - .withGetPublicKey(Arrangement.MLS_PUBLIC_KEY) + .withGetPublicKey(Arrangement.MLS_PUBLIC_KEY, Arrangement.MLS_CIPHER_SUITE) .withRegisterMLSClient(Either.Right(Unit)) .withKeyPackageLimits(Arrangement.REFILL_AMOUNT) .withUploadKeyPackagesSuccessful() @@ -119,7 +119,7 @@ class RegisterMLSClientUseCaseTest { val (arrangement, registerMLSClient) = Arrangement() .withGetMLSClientSuccessful() .withGettingE2EISettingsReturns(Either.Right(E2EI_TEAM_SETTINGS.copy(isRequired = e2eiIsRequired))) - .withGetPublicKey(Arrangement.MLS_PUBLIC_KEY) + .withGetPublicKey(Arrangement.MLS_PUBLIC_KEY, Arrangement.MLS_CIPHER_SUITE) .withRegisterMLSClient(Either.Right(Unit)) .withKeyPackageLimits(Arrangement.REFILL_AMOUNT) .withUploadKeyPackagesSuccessful() @@ -201,11 +201,11 @@ class RegisterMLSClientUseCaseTest { .thenReturn(Either.Right(Unit)) } - fun withGetPublicKey(result: ByteArray) = apply { + fun withGetPublicKey(publicKey: ByteArray, cipherSuite: UShort) = apply { given(mlsClient) .suspendFunction(mlsClient::getPublicKey) .whenInvoked() - .thenReturn(result) + .thenReturn(publicKey to cipherSuite) } fun withGetMLSClientSuccessful() = apply { @@ -225,6 +225,7 @@ class RegisterMLSClientUseCaseTest { companion object { val MLS_PUBLIC_KEY = "public_key".encodeToByteArray() + val MLS_CIPHER_SUITE = 0.toUShort() const val REFILL_AMOUNT = 100 val RANDOM_URL = "https://random.rn" val E2EI_TEAM_SETTINGS = E2EISettings( 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 c04d436e503..ce9219916e6 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,6 +118,14 @@ data class UpdateClientCapabilitiesRequest( @Serializable enum class MLSPublicKeyTypeDTO { + @SerialName("p256") + P256, + @SerialName("p384") + P384, + @SerialName("p521") + P521, + @SerialName("ed448") + ED448, @SerialName("ed25519") ED25519; }