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(mls-e2ei): adopt remaining E2EI functions (WPB-2928) #2156

Merged
merged 13 commits into from
Oct 23, 2023
3 changes: 3 additions & 0 deletions cryptography/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ kotlin {

// Libsodium
implementation(libs.libsodiumBindingsMP)

// UUIDs
implementation(libs.benAsherUUID)
}
}
val commonTest by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,22 @@ class MLSClientImpl(
TODO("Not yet implemented")
}

override suspend fun isE2EIEnabled(): Boolean {
TODO("Not yet implemented")
}

override suspend fun e2eiRotateAll(enrollment: E2EIClient, certificateChain: CertificateChain, newMLSKeyPackageCount: UInt) {
TODO("Not yet implemented")
}

override suspend fun isGroupVerified(groupId: MLSGroupId): Boolean {
override suspend fun isGroupVerified(groupId: MLSGroupId): E2EIConversationState {
TODO("Not supported on apple devices")
}

override suspend fun getUserIdentities(groupId: MLSGroupId, clients: List<E2EIQualifiedClientId>): List<WireIdentity> {
TODO("Not yet implemented")
}

companion object {
fun toUByteList(value: ByteArray): List<UByte> = value.asUByteArray().asList()
fun toUByteList(value: String): List<UByte> = value.encodeToByteArray().asUByteArray().asList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ class MLSClientImpl(
}

override suspend fun joinByExternalCommit(publicGroupState: ByteArray): CommitBundle {
return toCommitBundle(coreCrypto.joinByExternalCommit(
publicGroupState,
defaultGroupConfiguration,
MlsCredentialType.BASIC)
return toCommitBundle(
coreCrypto.joinByExternalCommit(
publicGroupState,
defaultGroupConfiguration,
MlsCredentialType.BASIC
)
)
}

Expand Down Expand Up @@ -137,9 +139,11 @@ class MLSClientImpl(
message
)

val messageBundle = listOf(toDecryptedMessageBundle(
decryptedMessage
))
val messageBundle = listOf(
toDecryptedMessageBundle(
decryptedMessage
)
)
val bufferedMessages = decryptedMessage.bufferedMessages?.map {
toDecryptedMessageBundle(it)
} ?: emptyList()
Expand Down Expand Up @@ -248,6 +252,10 @@ class MLSClientImpl(
coreCrypto.e2eiMlsInitOnly((enrollment as E2EIClientImpl).wireE2eIdentity, certificateChain)
}

override suspend fun isE2EIEnabled(): Boolean {
return coreCrypto.e2eiIsEnabled(defaultCiphersuite)
}

override suspend fun e2eiRotateAll(
enrollment: E2EIClient,
certificateChain: CertificateChain,
Expand All @@ -260,8 +268,17 @@ class MLSClientImpl(
)
}

override suspend fun isGroupVerified(groupId: MLSGroupId): Boolean =
coreCrypto.e2eiConversationState(groupId.decodeBase64Bytes()) != E2eiConversationState.DEGRADED
override suspend fun isGroupVerified(groupId: MLSGroupId): E2EIConversationState =
toE2EIConversationState(coreCrypto.e2eiConversationState(groupId.decodeBase64Bytes()))

override suspend fun getUserIdentities(groupId: MLSGroupId, clients: List<E2EIQualifiedClientId>): List<WireIdentity> {
val clientIds = clients.map {
it.toString().encodeToByteArray()
}
return coreCrypto.getUserIdentities(groupId.decodeBase64Bytes(), clientIds).map {
toIdentity(it)
}
}

companion object {
fun toUByteList(value: ByteArray): List<UByte> = value.asUByteArray().asList()
Expand All @@ -286,6 +303,29 @@ class MLSClientImpl(
toGroupInfoBundle(value.groupInfo)
)

fun toRotateBundle(value: com.wire.crypto.RotateBundle) = RotateBundle(
value.commits.map { (groupId, commitBundle) ->
toGroupId(groupId) to toCommitBundle(commitBundle)
}.toMap(),
value.newKeyPackages,
value.keyPackageRefsToRemove
)

fun toIdentity(value: com.wire.crypto.WireIdentity) = WireIdentity(
value.clientId,
value.handle,
value.displayName,
value.domain,
value.certificate
)

// TODO: remove later, when CoreCrypto return the groupId instead of Hex value
@Suppress("MagicNumber")
fun toGroupId(hexValue: String): MLSGroupId {
val byteArrayValue = hexValue.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
return toByteArray(toUByteList(byteArrayValue)).encodeBase64()
}

fun toGroupInfoBundle(value: com.wire.crypto.GroupInfoBundle) = GroupInfoBundle(
toEncryptionType(value.encryptionType),
toRatchetTreeType(value.ratchetTreeType),
Expand All @@ -303,13 +343,20 @@ class MLSClientImpl(
MlsRatchetTreeType.BY_REF -> RatchetTreeType.BY_REF
}

fun toE2EIConversationState(value: com.wire.crypto.E2eiConversationState) = when (value) {
E2eiConversationState.VERIFIED -> E2EIConversationState.VERIFIED
// TODO: this value is wrong on CoreCrypto, it will be renamed to NOT_VERIFIED
E2eiConversationState.DEGRADED -> E2EIConversationState.NOT_VERIFIED
E2eiConversationState.NOT_ENABLED -> E2EIConversationState.NOT_ENABLED
}

fun toDecryptedMessageBundle(value: DecryptedMessage) = DecryptedMessageBundle(
value.message,
value.commitDelay?.toLong(),
value.senderClientId?.let { CryptoQualifiedClientId.fromEncodedString(String(it)) },
value.hasEpochChanged,
value.identity?.let {
E2EIdentity(it.clientId, it.handle, it.displayName, it.domain)
WireIdentity(it.clientId, it.handle, it.displayName, it.domain, it.certificate)
}
)

Expand All @@ -319,9 +366,8 @@ class MLSClientImpl(
value.senderClientId?.let { CryptoQualifiedClientId.fromEncodedString(String(it)) },
value.hasEpochChanged,
value.identity?.let {
E2EIdentity(it.clientId, it.handle, it.displayName, it.domain)
WireIdentity(it.clientId, it.handle, it.displayName, it.domain, it.certificate)
}
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

package com.wire.kalium.cryptography

import com.benasher44.uuid.uuidFrom
import io.ktor.util.encodeBase64

typealias MLSGroupId = String

data class CryptoClientId(val value: String) {
Expand Down Expand Up @@ -70,16 +73,35 @@ data class CryptoQualifiedClientId(
}
}

data class E2EIdentity(
data class WireIdentity(
var clientId: String,
var handle: String,
var displayName: String,
var domain: String
var domain: String,
var certificate: String
)

@Suppress("MagicNumber")
data class E2EIQualifiedClientId(
val value: String,
val userId: CryptoQualifiedID
) {
override fun toString() = "${userId.value}:${value}@${userId.domain}"
override fun toString(): String {
val sourceUUID = uuidFrom(userId.value)

// Convert the UUID to bytes
val uuidBytes = ByteArray(16)
val mostSigBits = sourceUUID.mostSignificantBits
val leastSigBits = sourceUUID.leastSignificantBits

for (i in 0..7) {
uuidBytes[i] = ((mostSigBits shr (56 - i * 8)) and 0xFF).toByte()
uuidBytes[i + 8] = ((leastSigBits shr (56 - i * 8)) and 0xFF).toByte()
}

// Base64url encode the UUID bytes without padding
val base64UrlEncoded = uuidBytes.encodeBase64().removeSuffix("==")

return "${base64UrlEncoded}:${value}@${userId.domain}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ enum class RatchetTreeType {
BY_REF
}

enum class E2EIConversationState {
VERIFIED, NOT_VERIFIED, NOT_ENABLED;
}

open class GroupInfoBundle(
var encryptionType: GroupInfoEncryptionType,
var ratchetTreeType: RatchetTreeType,
Expand All @@ -50,12 +54,18 @@ open class CommitBundle(
val groupInfoBundle: GroupInfoBundle
)

open class RotateBundle(
var commits: Map<MLSGroupId, CommitBundle>,
var newKeyPackages: List<ByteArray>,
var keyPackageRefsToRemove: List<ByteArray>
)

class DecryptedMessageBundle(
val message: ByteArray?,
val commitDelay: Long?,
val senderClientId: CryptoQualifiedClientId?,
val hasEpochChanged: Boolean,
val identity: E2EIdentity?
val identity: WireIdentity?
)

@JvmInline
Expand Down Expand Up @@ -306,6 +316,13 @@ interface MLSClient {
*/
suspend fun e2eiMlsInitOnly(enrollment: E2EIClient, certificateChain: CertificateChain)

/**
* The E2EI State for the current MLS Client
*
* @return the E2EI state for the current MLS Client
*/
suspend fun isE2EIEnabled(): Boolean

/**
* Generate new keypackages after E2EI certificate issued
*/
Expand All @@ -316,5 +333,15 @@ interface MLSClient {
*
* @return the conversation verification status
*/
suspend fun isGroupVerified(groupId: MLSGroupId): Boolean
suspend fun isGroupVerified(groupId: MLSGroupId): E2EIConversationState

/**
* Get the identity of given clients in the given conversation
*
* @param clients a list of E2EIClientId of the requested clients
* @param groupId MLS group ID for an existing conversation
*
* @return the exist identities for requested clients
*/
suspend fun getUserIdentities(groupId: MLSGroupId, clients: List<E2EIQualifiedClientId>): List<WireIdentity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class E2EIClientTest : BaseMLSClientTest() {
companion object {

val ALICE1 = SampleUser(
CryptoQualifiedID("t6wRpI8BRSeviBwwiFp5MQ", "wire.com"), CryptoClientId("fb4b58152e20"), "Alice", "Alice_wire"
CryptoQualifiedID("837655f7-b448-465a-b4b2-93f0919b38f0", "elna.wire.link"), CryptoClientId("fb4b58152e20"), "Mojtaba Chenani", "mojtaba_wire"
)

val ACME_DIRECTORY_API_RESPONSE = """
Expand Down Expand Up @@ -187,7 +187,7 @@ class E2EIClientTest : BaseMLSClientTest() {
"identifiers":[
{
"type":"wireapp-id",
"value":"{\"name\":\"mojtaba chenani staging\",\"domain\":\"staging.zinfra.io\",\"client-id\":\"im:wireapp=ZTE1Mjc0MzEyNDQ0NGJhY2E1NWZmNjA2ZTk1MjIyMzM/[email protected]\",\"handle\":\"im:wireapp=mjstaging\"}"
"value":"{\"name\":\"Mojtaba Chenani\",\"domain\":\"elna.wire.link\",\"client-id\":\"im:wireapp=IG9YvzuWQIKUaRk12F5CIQ/[email protected]\",\"handle\":\"im:wireapp=mojtaba_wire\"}"
}
],
"notBefore":"2023-05-07T12:00:50.1666Z",
Expand Down Expand Up @@ -219,7 +219,7 @@ class E2EIClientTest : BaseMLSClientTest() {
"expires": "3000-06-07T10:22:49Z",
"identifier": {
"type": "wireapp-id",
"value": "{\"name\":\"Mojtaba Chenani\",\"domain\":\"anta.wire.link\",\"client-id\":\"im:wireapp=ZTk5ZjQ1NGNmNjdiNDQwMjhhNGUwYWVjZmNjZjBmMDU/89f1c4056c99edcb@anta.wire.link\",\"handle\":\"im:wireapp=mojtaba_wire\"}"
"value":"{\"name\":\"Mojtaba Chenani\",\"domain\":\"elna.wire.link\",\"client-id\":\"im:wireapp=IG9YvzuWQIKUaRk12F5CIQ/953218e68a63641f@elna.wire.link\",\"handle\":\"im:wireapp=mojtaba_wire\"}"
},
"status": "pending",
"wildcard": false
Expand Down Expand Up @@ -259,7 +259,7 @@ class E2EIClientTest : BaseMLSClientTest() {
"identifiers": [
{
"type": "wireapp-id",
"value": "{\"name\":\"Smith, Alice M (QA)\",\"domain\":\"example.com\",\"client-id\":\"impp:wireapp=NjJiYTRjMTIyODJjNDY5YmE5NGZmMjhhNjFkODA0Njk/[email protected]\",\"handle\":\"impp:wireapp=[email protected]\"}"
"value":"{\"name\":\"Mojtaba Chenani\",\"domain\":\"elna.wire.link\",\"client-id\":\"im:wireapp=IG9YvzuWQIKUaRk12F5CIQ/[email protected]\",\"handle\":\"im:wireapp=mojtaba_wire\"}"
}
],
"authorizations": [
Expand All @@ -277,7 +277,7 @@ class E2EIClientTest : BaseMLSClientTest() {
"identifiers": [
{
"type": "wireapp-id",
"value": "{\"name\":\"Smith, Alice M (QA)\",\"domain\":\"example.com\",\"client-id\":\"impp:wireapp=NjJiYTRjMTIyODJjNDY5YmE5NGZmMjhhNjFkODA0Njk/[email protected]\",\"handle\":\"impp:wireapp=[email protected]\"}"
"value":"{\"name\":\"Mojtaba Chenani\",\"domain\":\"elna.wire.link\",\"client-id\":\"im:wireapp=IG9YvzuWQIKUaRk12F5CIQ/[email protected]\",\"handle\":\"im:wireapp=mojtaba_wire\"}"
}
],
"authorizations": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ class E2EIQualifiedIDTest {

companion object {
private const val CLIENT_ID = "client_id"
private const val USER_ID = "user_id"
private const val USER_ID = "41d2b365-f4a9-4ba1-bddf-5afb8aca6786"
private const val DOMAIN = "domain"
private const val ENCODED_USER_ID = "QdKzZfSpS6G931r7ispnhg"

val ENCODED_E2EI_QUALIFIED_CLIENT_ID = "${USER_ID}:$CLIENT_ID@$DOMAIN"
const val ENCODED_E2EI_QUALIFIED_CLIENT_ID = "${ENCODED_USER_ID}:$CLIENT_ID@$DOMAIN"

val E2EI_QUALIFIED_CLIENT_ID = E2EIQualifiedClientId(CLIENT_ID, CryptoQualifiedID(USER_ID, DOMAIN))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ class MLSClientImpl : MLSClient {
TODO("Not yet implemented")
}

override suspend fun isE2EIEnabled(): Boolean {
TODO("Not yet implemented")
}

override suspend fun e2eiRotateAll(
enrollment: E2EIClient,
certificateChain: CertificateChain,
Expand All @@ -136,7 +140,11 @@ class MLSClientImpl : MLSClient {
TODO("Not yet implemented")
}

override suspend fun isGroupVerified(groupId: MLSGroupId): Boolean {
override suspend fun isGroupVerified(groupId: MLSGroupId): E2EIConversationState {
TODO("Not supported on js")
}

override suspend fun getUserIdentities(groupId: MLSGroupId, clients: List<E2EIQualifiedClientId>): List<WireIdentity> {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

@file:Suppress("TooManyFunctions")
package com.wire.kalium.logic.data.conversation

import com.wire.kalium.cryptography.E2EIConversationState
import com.wire.kalium.logic.data.connection.ConnectionStatusMapper
import com.wire.kalium.logic.data.id.IdMapper
import com.wire.kalium.logic.data.id.NetworkQualifiedId
Expand Down Expand Up @@ -56,7 +57,6 @@ import kotlinx.datetime.toInstant
import kotlin.time.DurationUnit
import kotlin.time.toDuration

@Suppress("TooManyFunctions")
interface ConversationMapper {
fun fromApiModelToDaoModel(apiModel: ConversationResponse, mlsGroupState: GroupState?, selfUserTeamId: TeamId?): ConversationEntity
fun fromDaoModel(daoModel: ConversationViewEntity): Conversation
Expand Down Expand Up @@ -545,3 +545,9 @@ internal fun Protocol.toModel(): Conversation.Protocol = when (this) {
Protocol.MIXED -> Conversation.Protocol.MIXED
Protocol.MLS -> Conversation.Protocol.MLS
}

internal fun E2EIConversationState.toModel(): Conversation.VerificationStatus = when (this) {
E2EIConversationState.VERIFIED -> Conversation.VerificationStatus.VERIFIED
E2EIConversationState.NOT_VERIFIED -> Conversation.VerificationStatus.NOT_VERIFIED
E2EIConversationState.NOT_ENABLED -> Conversation.VerificationStatus.NOT_VERIFIED
}
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,7 @@ internal class MLSConversationDataSource(
override suspend fun getConversationVerificationStatus(groupID: GroupID): Either<CoreFailure, Conversation.VerificationStatus> =
mlsClientProvider.getMLSClient().flatMap { mlsClient ->
wrapMLSRequest { mlsClient.isGroupVerified(idMapper.toCryptoModel(groupID)) }
}.map {
if (it) Conversation.VerificationStatus.VERIFIED
else Conversation.VerificationStatus.NOT_VERIFIED
}
}.map { it.toModel() }

private suspend fun retryOnCommitFailure(
groupID: GroupID,
Expand Down
Loading
Loading