diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepository.kt index 126a095d1eb..c490499194b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepository.kt @@ -18,7 +18,6 @@ package com.wire.kalium.logic.data.e2ei import com.wire.kalium.logic.CoreFailure -import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.wrapApiRequest import com.wire.kalium.network.api.base.unbound.acme.ACMEApi @@ -26,7 +25,7 @@ import com.wire.kalium.persistence.config.CRLUrlExpirationList import com.wire.kalium.persistence.config.CRLWithExpiration import com.wire.kalium.persistence.dao.MetadataDAO -interface CertificateRevocationListRepository { +internal interface CertificateRevocationListRepository { /** * Returns CRLs with expiration time. @@ -40,8 +39,7 @@ interface CertificateRevocationListRepository { internal class CertificateRevocationListRepositoryDataSource( private val acmeApi: ACMEApi, - private val metadataDAO: MetadataDAO, - private val userConfigRepository: UserConfigRepository + private val metadataDAO: MetadataDAO ) : CertificateRevocationListRepository { override suspend fun getCRLs(): CRLUrlExpirationList? = metadataDAO.getSerializable(CRL_LIST_KEY, CRLUrlExpirationList.serializer()) 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 f8b5ae82b20..989072e7675 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 @@ -211,6 +211,7 @@ import com.wire.kalium.logic.feature.e2ei.ACMECertificatesSyncWorker import com.wire.kalium.logic.feature.e2ei.ACMECertificatesSyncWorkerImpl import com.wire.kalium.logic.feature.e2ei.CertificateRevocationListCheckWorker import com.wire.kalium.logic.feature.e2ei.CertificateRevocationListCheckWorkerImpl +import com.wire.kalium.logic.feature.e2ei.CheckCrlRevocationListUseCase import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCase import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCaseImpl import com.wire.kalium.logic.feature.featureConfig.FeatureFlagSyncWorkerImpl @@ -1557,8 +1558,7 @@ class UserSessionScope internal constructor( private val certificateRevocationListRepository: CertificateRevocationListRepository get() = CertificateRevocationListRepositoryDataSource( acmeApi = globalScope.unboundNetworkContainer.acmeApi, - metadataDAO = userStorage.database.metadataDAO, - userConfigRepository = userConfigRepository + metadataDAO = userStorage.database.metadataDAO ) private val proteusPreKeyRefiller: ProteusPreKeyRefiller @@ -1862,10 +1862,11 @@ class UserSessionScope internal constructor( get() = MarkFileSharingChangeAsNotifiedUseCase(userConfigRepository) val isMLSEnabled: IsMLSEnabledUseCase get() = IsMLSEnabledUseCaseImpl(featureSupport, userConfigRepository) - val isE2EIEnabled: IsE2EIEnabledUseCase get() = IsE2EIEnabledUseCaseImpl( - userConfigRepository, - isMLSEnabled - ) + val isE2EIEnabled: IsE2EIEnabledUseCase + get() = IsE2EIEnabledUseCaseImpl( + userConfigRepository, + isMLSEnabled + ) val getDefaultProtocol: GetDefaultProtocolUseCase get() = GetDefaultProtocolUseCaseImpl( @@ -1959,6 +1960,13 @@ class UserSessionScope internal constructor( } } + val checkCrlRevocationList: CheckCrlRevocationListUseCase + get() = CheckCrlRevocationListUseCase( + certificateRevocationListRepository, + checkRevocationList, + userScopedLogger + ) + internal val getProxyCredentials: GetProxyCredentialsUseCase get() = GetProxyCredentialsUseCaseImpl(sessionManager) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCase.kt new file mode 100644 index 00000000000..43eafc8768a --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCase.kt @@ -0,0 +1,50 @@ +/* + * 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.feature.e2ei + +import com.wire.kalium.logger.KaliumLogger +import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository +import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCase +import com.wire.kalium.logic.functional.map +import kotlinx.datetime.Clock + +/** + * Use case to check the certificate revocation list (CRL) for expired entries. + * param forceUpdate: if true, the CRL will be checked even if it is not expired. + */ +class CheckCrlRevocationListUseCase internal constructor( + private val certificateRevocationListRepository: CertificateRevocationListRepository, + private val checkRevocationList: CheckRevocationListUseCase, + kaliumLogger: KaliumLogger +) { + + private val logger = kaliumLogger.withTextTag("CheckCrlRevocationListUseCase") + + suspend operator fun invoke(forceUpdate: Boolean) { + logger.i("Checking certificate revocation list (CRL). Force update: $forceUpdate") + certificateRevocationListRepository.getCRLs()?.cRLWithExpirationList?.forEach { crl -> + if (forceUpdate || (crl.expiration < Clock.System.now().epochSeconds.toULong())) { + checkRevocationList(crl.url).map { newExpirationTime -> + newExpirationTime?.let { + certificateRevocationListRepository.addOrUpdateCRL(crl.url, it) + } + } + } + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt index 701e98c83e4..bf1933b7597 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt @@ -17,11 +17,10 @@ */ package com.wire.kalium.logic.data.e2ei -import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepositoryDataSource.Companion.CRL_LIST_KEY import com.wire.kalium.network.api.base.unbound.acme.ACMEApi -import com.wire.kalium.persistence.config.CRLWithExpiration import com.wire.kalium.persistence.config.CRLUrlExpirationList +import com.wire.kalium.persistence.config.CRLWithExpiration import com.wire.kalium.persistence.dao.MetadataDAO import io.mockative.Mock import io.mockative.classOf @@ -50,6 +49,7 @@ class CertificateRevocationListRepositoryTest { ) }.wasInvoked(once) } + @Test fun givenNoStoredList_whenUpdatingCRLs_thenAddNewCRL() = runTest { val (arrangement, crlRepository) = Arrangement() @@ -114,10 +114,7 @@ class CertificateRevocationListRepositoryTest { @Mock val metadataDAO = mock(classOf()) - @Mock - val userConfigRepository = mock(classOf()) - - fun arrange() = this to CertificateRevocationListRepositoryDataSource(acmeApi, metadataDAO, userConfigRepository) + fun arrange() = this to CertificateRevocationListRepositoryDataSource(acmeApi, metadataDAO) suspend fun withEmptyList() = apply { given(metadataDAO).coroutine { @@ -127,6 +124,7 @@ class CertificateRevocationListRepositoryTest { ) }.thenReturn(CRLUrlExpirationList(listOf())) } + suspend fun withNullCRLResult() = apply { given(metadataDAO).coroutine { metadataDAO.getSerializable( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckWorkerTest.kt similarity index 98% rename from logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckTest.kt rename to logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckWorkerTest.kt index 62a9e580c43..9a5a42d18d7 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckWorkerTest.kt @@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import kotlin.test.Test -class CertificateRevocationListCheckTest { +class CertificateRevocationListCheckWorkerTest { @Test fun givenExpiredCRL_whenTimeElapses_thenCheckRevocationList() = runTest { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCaseTest.kt new file mode 100644 index 00000000000..9f3859f1ab8 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCaseTest.kt @@ -0,0 +1,137 @@ +/* + * 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.feature.e2ei + +import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository +import com.wire.kalium.logic.data.sync.IncrementalSyncRepository +import com.wire.kalium.logic.data.sync.IncrementalSyncStatus +import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCase +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.kaliumLogger +import com.wire.kalium.persistence.config.CRLUrlExpirationList +import com.wire.kalium.persistence.config.CRLWithExpiration +import io.mockative.Mock +import io.mockative.any +import io.mockative.classOf +import io.mockative.eq +import io.mockative.given +import io.mockative.mock +import io.mockative.once +import io.mockative.verify +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class CheckCrlRevocationListUseCaseTest { + + @Test + fun givenExpiredCRL_whenTimeElapses_thenCheckRevocationList() = runTest { + val (arrangement, checkCrlWorker) = Arrangement() + .withExpiredCRL() + .withCheckRevocationListResult() + .arrange() + + checkCrlWorker(false) + + verify(arrangement.certificateRevocationListRepository) + .suspendFunction(arrangement.certificateRevocationListRepository::getCRLs) + .wasInvoked(exactly = once) + + verify(arrangement.checkRevocationList) + .suspendFunction(arrangement.checkRevocationList::invoke) + .with(eq(DUMMY_URL)) + .wasInvoked(exactly = once) + + verify(arrangement.certificateRevocationListRepository) + .suspendFunction(arrangement.certificateRevocationListRepository::addOrUpdateCRL) + .with(eq(DUMMY_URL), eq(FUTURE_TIMESTAMP)) + .wasInvoked(exactly = once) + + } + + @Test + fun givenForceIsTrue_thenCheckRevicationEvenIfTimeDidnotElapse() = runTest { + val (arrangement, checkCrlWorker) = Arrangement() + .withNonExpiredCRL() + .withCheckRevocationListResult() + .arrange() + + checkCrlWorker(true) + + verify(arrangement.certificateRevocationListRepository) + .suspendFunction(arrangement.certificateRevocationListRepository::getCRLs) + .wasInvoked(exactly = once) + + verify(arrangement.checkRevocationList) + .suspendFunction(arrangement.checkRevocationList::invoke) + .with(eq(DUMMY_URL)) + .wasInvoked(exactly = once) + + verify(arrangement.certificateRevocationListRepository) + .suspendFunction(arrangement.certificateRevocationListRepository::addOrUpdateCRL) + .with(eq(DUMMY_URL), eq(FUTURE_TIMESTAMP)) + .wasInvoked(exactly = once) + } + + private class Arrangement { + + @Mock + val certificateRevocationListRepository = mock(classOf()) + + @Mock + val checkRevocationList = mock(classOf()) + + fun arrange() = this to CheckCrlRevocationListUseCase( + certificateRevocationListRepository, checkRevocationList, kaliumLogger + ) + + fun withNoCRL() = apply { + given(certificateRevocationListRepository) + .suspendFunction(certificateRevocationListRepository::getCRLs) + .whenInvoked() + .thenReturn(null) + } + + fun withNonExpiredCRL() = apply { + given(certificateRevocationListRepository) + .suspendFunction(certificateRevocationListRepository::getCRLs) + .whenInvoked() + .thenReturn(CRLUrlExpirationList(listOf(CRLWithExpiration(DUMMY_URL, FUTURE_TIMESTAMP)))) + } + + fun withExpiredCRL() = apply { + given(certificateRevocationListRepository) + .suspendFunction(certificateRevocationListRepository::getCRLs) + .whenInvoked() + .thenReturn(CRLUrlExpirationList(listOf(CRLWithExpiration(DUMMY_URL, TIMESTAMP)))) + } + fun withCheckRevocationListResult() = apply { + given(checkRevocationList) + .suspendFunction(checkRevocationList::invoke) + .whenInvokedWith(any()) + .thenReturn(Either.Right(FUTURE_TIMESTAMP)) + } + } + + companion object { + const val DUMMY_URL = "https://dummy.url" + val TIMESTAMP = 633218892.toULong() // Wednesday, 24 January 1990 22:08:12 + val FUTURE_TIMESTAMP = 4104511692.toULong() // Sunday, 24 January 2100 22:08:12 + } +}