Skip to content

Commit

Permalink
feat: add a use case to check CRL revocation list once (#2662)
Browse files Browse the repository at this point in the history
* feat: add a crl revocation list to debug screen

* add tests

* add docs
  • Loading branch information
MohamadJaara authored Mar 21, 2024
1 parent cccff38 commit e9c9ef1
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
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
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.
Expand All @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -1959,6 +1960,13 @@ class UserSessionScope internal constructor(
}
}

val checkCrlRevocationList: CheckCrlRevocationListUseCase
get() = CheckCrlRevocationListUseCase(
certificateRevocationListRepository,
checkRevocationList,
userScopedLogger
)

internal val getProxyCredentials: GetProxyCredentialsUseCase
get() = GetProxyCredentialsUseCaseImpl(sessionManager)

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -50,6 +49,7 @@ class CertificateRevocationListRepositoryTest {
)
}.wasInvoked(once)
}

@Test
fun givenNoStoredList_whenUpdatingCRLs_thenAddNewCRL() = runTest {
val (arrangement, crlRepository) = Arrangement()
Expand Down Expand Up @@ -114,10 +114,7 @@ class CertificateRevocationListRepositoryTest {
@Mock
val metadataDAO = mock(classOf<MetadataDAO>())

@Mock
val userConfigRepository = mock(classOf<UserConfigRepository>())

fun arrange() = this to CertificateRevocationListRepositoryDataSource(acmeApi, metadataDAO, userConfigRepository)
fun arrange() = this to CertificateRevocationListRepositoryDataSource(acmeApi, metadataDAO)

suspend fun withEmptyList() = apply {
given(metadataDAO).coroutine {
Expand All @@ -127,6 +124,7 @@ class CertificateRevocationListRepositoryTest {
)
}.thenReturn(CRLUrlExpirationList(listOf()))
}

suspend fun withNullCRLResult() = apply {
given(metadataDAO).coroutine {
metadataDAO.getSerializable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CertificateRevocationListRepository>())

@Mock
val checkRevocationList = mock(classOf<CheckRevocationListUseCase>())

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
}
}

0 comments on commit e9c9ef1

Please sign in to comment.