Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(e2ei): separate e2ei enrollment flow (WPB-5229) #2223

Merged
merged 21 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4fe3c41
chore: separate e2ei enrollment steps
mchenani Nov 10, 2023
a33e34e
Merge branch 'develop' of https://github.com/wireapp/kalium into feat…
mchenani Nov 10, 2023
4c05472
Merge branch 'develop' of https://github.com/wireapp/kalium into feat…
mchenani Nov 14, 2023
bf09674
chore: refactor and added new error types for e2eiFailure
mchenani Nov 14, 2023
b24687e
Merge branch 'develop' of https://github.com/wireapp/kalium into feat…
mchenani Nov 14, 2023
473d2b0
Merge branch 'develop' into feat/e2ei/separate-e2ei-enrollment-steps
mchenani Nov 14, 2023
94e71fa
fix detekt
mchenani Nov 14, 2023
c8c284c
Merge remote-tracking branch 'origin/feat/e2ei/separate-e2ei-enrollme…
mchenani Nov 14, 2023
3ab00ac
chore: get acme url from team settings
mchenani Nov 15, 2023
8ac5d47
fix detekt
mchenani Nov 15, 2023
e2f1028
introduce new inLine function to Either
mchenani Nov 15, 2023
5e8f3c6
fix detekt
mchenani Nov 15, 2023
6580367
fix tests
mchenani Nov 15, 2023
56e6698
fix tests
mchenani Nov 15, 2023
ab5d8b4
Merge branch 'develop' of https://github.com/wireapp/kalium into feat…
mchenani Nov 16, 2023
cd5f1c8
Merge branch 'develop' into feat/e2ei/separate-e2ei-enrollment-steps
mchenani Nov 16, 2023
f78170b
cover comments
mchenani Nov 16, 2023
6493796
Merge remote-tracking branch 'origin/feat/e2ei/separate-e2ei-enrollme…
mchenani Nov 16, 2023
22ac20b
Merge branch 'develop' into feat/e2ei/separate-e2ei-enrollment-steps
mchenani Nov 17, 2023
bc138bd
fix detekt
mchenani Nov 17, 2023
36e7112
Merge remote-tracking branch 'origin/feat/e2ei/separate-e2ei-enrollme…
mchenani Nov 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ class E2EIClientImpl(
)

private fun toAcmeChallenge(value: com.wire.crypto.AcmeChallenge) = AcmeChallenge(
value.delegate.toUByteArray().asByteArray(), value.url
value.delegate.toUByteArray().asByteArray(),
value.url,
value.target
)

fun toNewAcmeAuthz(value: com.wire.crypto.NewAcmeAuthz) = NewAcmeAuthz(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ data class NewAcmeOrder(

data class AcmeChallenge(
var delegate: JsonRawData,
var url: String
var url: String,
var target: String
)
mchenani marked this conversation as resolved.
Show resolved Hide resolved

data class NewAcmeAuthz(
Expand Down
14 changes: 11 additions & 3 deletions logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.wire.kalium.logic

import com.wire.kalium.cryptography.exceptions.ProteusException
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.e2ei.usecase.E2EIEnrollmentResult
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.network.exceptions.APINotSupported
import com.wire.kalium.network.exceptions.KaliumException
Expand Down Expand Up @@ -107,6 +108,7 @@ sealed interface CoreFailure {
* the event is skipped and the sync continues
*/
data object FeatureNotImplemented : FeatureFailure()

/**
* No common Protocol found in order to establish a conversation between parties.
* Could be, for example, that the desired user only supports Proteus, but we only support MLS.
Expand Down Expand Up @@ -196,9 +198,15 @@ interface MLSFailure : CoreFailure {
}
}

class E2EIFailure(internal val exception: Exception) : CoreFailure {
interface E2EIFailure : CoreFailure {
data class FailedInitialization(val step: E2EIEnrollmentResult.E2EIStep) : E2EIFailure
data class FailedOAuth(val reason: String) : E2EIFailure
data class FailedFinalization(val step: E2EIEnrollmentResult.E2EIStep) : E2EIFailure
data object FailedRotationAndMigration : E2EIFailure

val rootCause: Throwable get() = exception
class Generic(internal val exception: Exception) : E2EIFailure {
val rootCause: Throwable get() = exception
}
}

class ProteusFailure(internal val proteusException: ProteusException) : CoreFailure {
Expand Down Expand Up @@ -306,7 +314,7 @@ internal inline fun <T> wrapE2EIRequest(e2eiRequest: () -> T): Either<E2EIFailur
Either.Right(e2eiRequest())
} catch (e: Exception) {
kaliumLogger.e(e.stackTraceToString())
Either.Left(E2EIFailure(e))
Either.Left(E2EIFailure.Generic(e))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ internal class EI2EIClientProviderImpl(
private suspend fun getSelfUserInfo(): Either<CoreFailure, Pair<String, String>> {
val selfUser = userRepository.getSelfUser() ?: return Either.Left(CoreFailure.Unknown(NullPointerException()))
return if (selfUser.name == null || selfUser.handle == null)
Either.Left(E2EIFailure(IllegalArgumentException(ERROR_NAME_AND_HANDLE_MUST_NOT_BE_NULL)))
Either.Left(E2EIFailure.Generic(IllegalArgumentException(ERROR_NAME_AND_HANDLE_MUST_NOT_BE_NULL)))
else Either.Right(Pair(selfUser.name, selfUser.handle))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.wire.kalium.cryptography.AcmeDirectory
import com.wire.kalium.cryptography.NewAcmeAuthz
import com.wire.kalium.cryptography.NewAcmeOrder
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.data.client.E2EIClientProvider
import com.wire.kalium.logic.data.client.MLSClientProvider
import com.wire.kalium.logic.data.conversation.MLSConversationRepository
Expand Down Expand Up @@ -63,21 +64,27 @@ interface E2EIRepository {
suspend fun rotateKeysAndMigrateConversations(certificateChain: String): Either<CoreFailure, Unit>
}

@Suppress("LongParameterList")
class E2EIRepositoryImpl(
private val e2EIApi: E2EIApi,
private val acmeApi: ACMEApi,
private val e2EIClientProvider: E2EIClientProvider,
private val mlsClientProvider: MLSClientProvider,
private val currentClientIdProvider: CurrentClientIdProvider,
private val mlsConversationRepository: MLSConversationRepository
private val mlsConversationRepository: MLSConversationRepository,
private val userConfigRepository: UserConfigRepository
) : E2EIRepository {

override suspend fun loadACMEDirectories(): Either<CoreFailure, AcmeDirectory> = wrapApiRequest {
acmeApi.getACMEDirectories()
}.flatMap { directories ->
e2EIClientProvider.getE2EIClient().flatMap { e2eiClient ->
wrapE2EIRequest {
e2eiClient.directoryResponse(Json.encodeToString(directories).encodeToByteArray())
override suspend fun loadACMEDirectories(): Either<CoreFailure, AcmeDirectory> = userConfigRepository.getE2EISettings().flatMap {
wrapApiRequest {
// todo: remove after testing e2ei
val discoverUrl = "https://acme.elna.wire.link/acme/defaultteams"
mchenani marked this conversation as resolved.
Show resolved Hide resolved
acmeApi.getACMEDirectories(discoverUrl)
}.flatMap { directories ->
e2EIClientProvider.getE2EIClient().flatMap { e2eiClient ->
wrapE2EIRequest {
e2eiClient.directoryResponse(Json.encodeToString(directories).encodeToByteArray())
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,8 @@ class UserSessionScope internal constructor(
e2EIClientProvider,
mlsClientProvider,
clientIdProvider,
mlsConversationRepository
mlsConversationRepository,
userConfigRepository
)

private val e2EIClientProvider: E2EIClientProvider by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,38 @@
*/
package com.wire.kalium.logic.feature.e2ei.usecase

import com.wire.kalium.cryptography.NewAcmeAuthz
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.E2EIFailure
import com.wire.kalium.logic.data.e2ei.E2EIRepository
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.onFailure
import com.wire.kalium.logic.kaliumLogger
import kotlinx.coroutines.delay

/**
* Issue an E2EI certificate and re-initiate the MLSClient
*/
interface EnrollE2EIUseCase {
suspend operator fun invoke(idToken: String): Either<CoreFailure, E2EIEnrollmentResult>
suspend fun initialEnrollment(): Either<CoreFailure, E2EIEnrollmentResult>
suspend fun finalizeEnrollment(
idToken: String,
initializationResult: E2EIEnrollmentResult.Initialized
): Either<CoreFailure, E2EIEnrollmentResult>
}

@Suppress("ReturnCount")
class EnrollE2EIUseCaseImpl internal constructor(
private val e2EIRepository: E2EIRepository,
) : EnrollE2EIUseCase {
/**
* Operation to issue an E2EI certificate and re-initiate MLS Client
* Operation to initial E2EI certificate enrollment
*
* @param idToken id token generated by the IdP
* @return [Either] [CoreFailure] or [E2EIEnrollmentResult]
*/
override suspend fun invoke(idToken: String): Either<CoreFailure, E2EIEnrollmentResult> {
override suspend fun initialEnrollment(): Either<CoreFailure, E2EIEnrollmentResult> {
kaliumLogger.i("start E2EI Enrollment Initialization")

val acmeDirectories = e2EIRepository.loadACMEDirectories().fold({
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.AcmeDirectories, it).toEitherLeft()
Expand All @@ -65,7 +72,31 @@ class EnrollE2EIUseCaseImpl internal constructor(
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.AcmeNewAuthz, it).toEitherLeft()
}, { it })
mchenani marked this conversation as resolved.
Show resolved Hide resolved

prevNonce = authzResponse.second
val initializationResult = E2EIEnrollmentResult.Initialized(
authzResponse.first.wireOidcChallenge!!.target, authzResponse.first, authzResponse.second, newOrderResponse.third
)

kaliumLogger.i("E2EI Enrollment Initialization Result: $initializationResult")

return Either.Right(initializationResult)
}

/**
* Operation to finalize E2EI certificate enrollment
*
* @param idToken id token generated by the IdP
* @param initializationResult e2ei initialization result
*
* @return [Either] [CoreFailure] or [E2EIEnrollmentResult]
*/
override suspend fun finalizeEnrollment(
idToken: String,
initializationResult: E2EIEnrollmentResult.Initialized
): Either<CoreFailure, E2EIEnrollmentResult> {

var prevNonce = initializationResult.lastNonce
val authz = initializationResult.authz
val orderLocation = initializationResult.orderLocation

val wireNonce = e2EIRepository.getWireNonce().fold({
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.WireNonce, it).toEitherLeft()
Expand All @@ -80,28 +111,27 @@ class EnrollE2EIUseCaseImpl internal constructor(
}, { it })

val dpopChallengeResponse = e2EIRepository.validateDPoPChallenge(
wireAccessToken.token, prevNonce, authzResponse.first.wireDpopChallenge!!
wireAccessToken.token, prevNonce, authz.wireDpopChallenge!!
).fold({
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.DPoPChallenge, it).toEitherLeft()
}, { it })

prevNonce = dpopChallengeResponse.nonce

val oidcChallengeResponse = e2EIRepository.validateOIDCChallenge(
idToken, prevNonce, authzResponse.first.wireOidcChallenge!!
idToken, prevNonce, authz.wireOidcChallenge!!
).fold({
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.OIDCChallenge, it).toEitherLeft()
}, { it })

prevNonce = oidcChallengeResponse.nonce

val orderResponse = e2EIRepository.checkOrderRequest(newOrderResponse.third, prevNonce).fold({
val orderResponse = e2EIRepository.checkOrderRequest(orderLocation, prevNonce).fold({
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.CheckOrderRequest, it).toEitherLeft()
}, { it })

prevNonce = orderResponse.first.nonce

// TODO(fix): replace with orderResponse.third
val finalizeResponse = e2EIRepository.finalize(orderResponse.second, prevNonce).fold({
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.FinalizeRequest, it).toEitherLeft()
}, { it })
Expand All @@ -116,7 +146,7 @@ class EnrollE2EIUseCaseImpl internal constructor(
return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.ConversationMigration, it).toEitherLeft()
}

return Either.Right(E2EIEnrollmentResult.Success(certificateRequest.response.decodeToString()))
return Either.Right(E2EIEnrollmentResult.Finalized(certificateRequest.response.decodeToString()))
}

}
Expand All @@ -128,6 +158,7 @@ sealed interface E2EIEnrollmentResult {
AcmeNewAccount,
AcmeNewOrder,
AcmeNewAuthz,
OAuth,
WireNonce,
DPoPToken,
WireAccessToken,
Expand All @@ -139,13 +170,15 @@ sealed interface E2EIEnrollmentResult {
Certificate
}

class Success(val certificate: String) : E2EIEnrollmentResult
class Initialized(val target: String, val authz: NewAcmeAuthz, val lastNonce: String, val orderLocation: String) : E2EIEnrollmentResult

class Finalized(val certificate: String) : E2EIEnrollmentResult

data class Failed(val step: E2EIStep, val failure: CoreFailure) : E2EIEnrollmentResult {
override fun toString(): String {
return "E2EI enrollment failed at $step: with $failure"
}

fun toEitherLeft() = Either.Left(E2EIFailure(Exception(this.toString())))
fun toEitherLeft() = Either.Left(E2EIFailure.Generic(Exception(this.toString())))
}
}
Loading
Loading