From 05578ed56f54e34bbd8436a43e3e7ec675ad8961 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 26 Jan 2024 09:23:53 +0100 Subject: [PATCH] feat: check revocation list on decryptMessage --- .../MLSClientImpl.kt | 6 +- .../com/wire/kalium/cryptography/MLSClient.kt | 3 +- .../DecryptedMessageBundleMapper.kt | 11 ++-- .../conversation/MLSConversationRepository.kt | 3 +- .../kalium/logic/feature/UserSessionScope.kt | 2 + .../message/MLSMessageUnpacker.kt | 15 +++++ .../MLSConversationRepositoryTest.kt | 3 +- .../message/MLSMessageUnpackerTest.kt | 60 ++++++++++++++++++- 8 files changed, 91 insertions(+), 12 deletions(-) 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 d01c1b7c7ad..732a1b84f24 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt @@ -387,7 +387,8 @@ class MLSClientImpl( value.commitDelay?.toLong(), value.senderClientId?.let { CryptoQualifiedClientId.fromEncodedString(String(it)) }, value.hasEpochChanged, - value.identity?.let { toIdentity(it) } + value.identity?.let { toIdentity(it) }, + value.crlNewDistributionPoints ) fun toDecryptedMessageBundle(value: BufferedDecryptedMessage) = DecryptedMessageBundle( @@ -395,7 +396,8 @@ class MLSClientImpl( value.commitDelay?.toLong(), value.senderClientId?.let { CryptoQualifiedClientId.fromEncodedString(String(it)) }, value.hasEpochChanged, - value.identity?.let { toIdentity(it) } + value.identity?.let { toIdentity(it) }, + value.crlNewDistributionPoints ) fun toCredentialType(value: CredentialType) = when (value) { 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 f391ebf0551..bf244862038 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt @@ -65,7 +65,8 @@ class DecryptedMessageBundle( val commitDelay: Long?, val senderClientId: CryptoQualifiedClientId?, val hasEpochChanged: Boolean, - val identity: WireIdentity? + val identity: WireIdentity?, + val crlNewDistributionPoints: List? ) @JvmInline diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/DecryptedMessageBundleMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/DecryptedMessageBundleMapper.kt index 79b4d623704..572df6d254e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/DecryptedMessageBundleMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/DecryptedMessageBundleMapper.kt @@ -22,8 +22,8 @@ import com.wire.kalium.logic.data.id.toModel fun com.wire.kalium.cryptography.DecryptedMessageBundle.toModel(groupID: GroupID): DecryptedMessageBundle = DecryptedMessageBundle( - groupID, - message?.let { message -> + groupID = groupID, + applicationMessage = message?.let { message -> // We will always have senderClientId together with an application message // but CoreCrypto API doesn't express this ApplicationMessage( @@ -32,13 +32,14 @@ fun com.wire.kalium.cryptography.DecryptedMessageBundle.toModel(groupID: GroupID senderClientID = senderClientId!!.toModel().clientId ) }, - commitDelay, - identity?.let { identity -> + commitDelay = commitDelay, + identity = identity?.let { identity -> E2EIdentity( identity.clientId, identity.handle, identity.displayName, identity.domain ) - } + }, + crlNewDistributionPoints = crlNewDistributionPoints ) 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 6c127f264a9..dfa441b5666 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 @@ -88,7 +88,8 @@ data class DecryptedMessageBundle( val groupID: GroupID, val applicationMessage: ApplicationMessage?, val commitDelay: Long?, - val identity: E2EIdentity? + val identity: E2EIdentity?, + val crlNewDistributionPoints: List? ) data class E2EIdentity(val clientId: String, val handle: String, val displayName: String, val domain: String) 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 bcf4ae7e67a..ca8a3ede338 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 @@ -1198,6 +1198,8 @@ class UserSessionScope internal constructor( subconversationRepository = subconversationRepository, mlsConversationRepository = mlsConversationRepository, pendingProposalScheduler = pendingProposalScheduler, + checkRevocationList = checkRevocationList, + certificateRevocationListRepository = certificateRevocationListRepository, selfUserId = userId ) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpacker.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpacker.kt index f09203c0992..6be60f27a56 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpacker.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpacker.kt @@ -26,6 +26,7 @@ import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.conversation.DecryptedMessageBundle import com.wire.kalium.logic.data.conversation.MLSConversationRepository import com.wire.kalium.logic.data.conversation.SubconversationRepository +import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID @@ -34,6 +35,7 @@ import com.wire.kalium.logic.data.message.ProtoContent import com.wire.kalium.logic.data.message.ProtoContentMapper 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.feature.message.PendingProposalScheduler import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap @@ -58,6 +60,8 @@ internal class MLSMessageUnpackerImpl( private val mlsConversationRepository: MLSConversationRepository, private val pendingProposalScheduler: PendingProposalScheduler, private val selfUserId: UserId, + private val checkRevocationList: CheckRevocationListUseCase, + private val certificateRevocationListRepository: CertificateRevocationListRepository, private val protoContentMapper: ProtoContentMapper = MapperProvider.protoContentMapper(selfUserId = selfUserId), ) : MLSMessageUnpacker { @@ -68,10 +72,21 @@ internal class MLSMessageUnpackerImpl( if (bundles.isEmpty()) return@map listOf(MessageUnpackResult.HandshakeMessage) bundles.map { bundle -> + checkRevocationList(bundle) unpackMlsBundle(bundle, event.conversationId, event.timestampIso.toInstant()) } } + private suspend fun checkRevocationList(bundle: DecryptedMessageBundle) { + bundle.crlNewDistributionPoints?.forEach { url -> + checkRevocationList(url).map { newExpiration -> + newExpiration?.let { + certificateRevocationListRepository.addOrUpdateCRL(url, newExpiration) + } + } + } + } + override suspend fun unpackMlsBundle( bundle: DecryptedMessageBundle, conversationId: ConversationId, 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 8a6dfc6dec3..ff29acb6fd6 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 @@ -1651,7 +1651,8 @@ class MLSConversationRepositoryTest { commitDelay = null, senderClientId = null, hasEpochChanged = true, - identity = null + identity = null, + crlNewDistributionPoints = null ) val MEMBER_JOIN_EVENT = EventContentDTO.Conversation.MemberJoinDTO( TestConversation.NETWORK_ID, diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpackerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpackerTest.kt index adc5a11c7c4..0b2e14dfecc 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpackerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageUnpackerTest.kt @@ -26,7 +26,9 @@ import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.conversation.DecryptedMessageBundle import com.wire.kalium.logic.data.conversation.MLSConversationRepository import com.wire.kalium.logic.data.conversation.SubconversationRepository +import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCase import com.wire.kalium.logic.feature.message.PendingProposalScheduler import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.framework.TestEvent @@ -45,6 +47,7 @@ import io.mockative.given import io.mockative.matching import io.mockative.mock import io.mockative.once +import io.mockative.twice import io.mockative.verify import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -138,12 +141,50 @@ class MLSMessageUnpackerTest { val messageEvent = TestEvent.newMLSMessageEvent(eventTimestamp) mlsUnpacker.unpackMlsMessage(messageEvent) + verify(arrangement.checkRevocationList) + .suspendFunction(arrangement.checkRevocationList::invoke) + .with(eq(DECRYPTED_MESSAGE_BUNDLE)) + .wasNotInvoked() + verify(arrangement.mlsConversationRepository) .suspendFunction(arrangement.mlsConversationRepository::decryptMessage) .with(matching { it.contentEquals(messageEvent.content.decodeBase64Bytes()) }, eq(TestConversation.GROUP_ID)) .wasInvoked(once) } + @Test + fun givenNewMLSMessageEventWithCrlNewDistributionPoints_whenUnpacking_thenCheckRevocationList() = runTest { + val eventTimestamp = DateTimeUtil.currentInstant() + val decryptedMessageBundleWithDistributionPoints = DECRYPTED_MESSAGE_BUNDLE.copy( + crlNewDistributionPoints = listOf("https://crl.wire.com/crl.pem", "https://crl2.wire.com/crl.pem") + ) + val (arrangement, mlsUnpacker) = Arrangement() + .withMLSClientProviderReturningClient() + .withGetConversationProtocolInfoSuccessful(TestConversation.MLS_CONVERSATION.protocol) + .withDecryptMessageReturning(Either.Right(listOf(decryptedMessageBundleWithDistributionPoints))) + .withCheckRevocationListReturning() + .arrange() + + val messageEvent = TestEvent.newMLSMessageEvent(eventTimestamp) + + mlsUnpacker.unpackMlsMessage(messageEvent) + + verify(arrangement.mlsConversationRepository) + .suspendFunction(arrangement.mlsConversationRepository::decryptMessage) + .with(matching { it.contentEquals(messageEvent.content.decodeBase64Bytes()) }, eq(TestConversation.GROUP_ID)) + .wasInvoked(once) + + verify(arrangement.checkRevocationList) + .suspendFunction(arrangement.checkRevocationList::invoke) + .with(any()) + .wasInvoked(twice) + + verify(arrangement.certificateRevocationListRepository) + .suspendFunction(arrangement.certificateRevocationListRepository::addOrUpdateCRL) + .with(any(), any()) + .wasInvoked(twice) + } + private class Arrangement { @Mock @@ -164,12 +205,20 @@ class MLSMessageUnpackerTest { @Mock val subconversationRepository = mock(classOf()) + @Mock + val checkRevocationList = mock(classOf()) + + @Mock + val certificateRevocationListRepository = mock(classOf()) + private val mlsMessageUnpacker = MLSMessageUnpackerImpl( conversationRepository, subconversationRepository, mlsConversationRepository, pendingProposalScheduler, - SELF_USER_ID + SELF_USER_ID, + checkRevocationList, + certificateRevocationListRepository ) fun withMLSClientProviderReturningClient() = apply { @@ -185,6 +234,12 @@ class MLSMessageUnpackerTest { .whenInvokedWith(anything(), anything()) .thenReturn(result) } + fun withCheckRevocationListReturning() = apply { + given(checkRevocationList) + .suspendFunction(checkRevocationList::invoke) + .whenInvokedWith(anything()) + .thenReturn(Either.Right(ULong.MIN_VALUE)) + } fun withScheduleCommitSucceeding() = apply { given(pendingProposalScheduler) @@ -208,7 +263,8 @@ class MLSMessageUnpackerTest { groupID = TestConversation.GROUP_ID, applicationMessage = null, commitDelay = null, - identity = null + identity = null, + crlNewDistributionPoints = null ) } }