All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.com.wire.crypto.client.MlsModel.kt Maven / Gradle / Ivy

There is a newer version: 1.0.2
Show newest version
package com.wire.crypto.client

import com.wire.crypto.MlsGroupInfoEncryptionType
import com.wire.crypto.MlsRatchetTreeType

@JvmInline
value class Ciphersuites(private val value: Set) {
    companion object {

        val DEFAULT = Ciphersuites(setOf(Ciphersuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519))
    }

    fun lower() = value.map { it.lower() }
}

enum class Ciphersuite {
    // DH KEM x25519 | AES-GCM 128 | SHA2-256 | Ed25519
    MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,

    // DH KEM P256 | AES-GCM 128 | SHA2-256 | EcDSA P256
    MLS_128_DHKEMP256_AES128GCM_SHA256_P256,

    // DH KEM x25519 | Chacha20Poly1305 | SHA2-256 | Ed25519
    MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,

    // DH KEM x448 | AES-GCM 256 | SHA2-512 | Ed448
    MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448,

    // DH KEM P521 | AES-GCM 256 | SHA2-512 | EcDSA P521
    MLS_256_DHKEMP521_AES256GCM_SHA512_P521,

    // DH KEM x448 | Chacha20Poly1305 | SHA2-512 | Ed448
    MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448,

    // DH KEM P384 | AES-GCM 256 | SHA2-384 | EcDSA P384
    MLS_256_DHKEMP384_AES256GCM_SHA384_P384;

    companion object {

        val DEFAULT = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519
    }

    fun lower() = (ordinal + 1).toUShort()
}

enum class CredentialType {
    Basic,
    X509;

    companion object {
        val DEFAULT = Basic
    }

    fun lower() = when (this) {
        Basic -> com.wire.crypto.MlsCredentialType.BASIC
        X509 -> com.wire.crypto.MlsCredentialType.X509
    }
}

fun com.wire.crypto.MlsCredentialType.lift() = when (this) {
    com.wire.crypto.MlsCredentialType.BASIC -> CredentialType.Basic
    com.wire.crypto.MlsCredentialType.X509 -> CredentialType.X509
}

@JvmInline
value class MLSGroupId(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toGroupId() = MLSGroupId(this)
fun String.toGroupId() = MLSGroupId(toByteArray())

@JvmInline
value class ClientId(override val value: String) : FfiType {

    override fun lower() = value.toByteArray()
}

@OptIn(ExperimentalUnsignedTypes::class)
fun com.wire.crypto.ClientId.toClientId() = ClientId(String(toUByteArray().asByteArray()))
fun String.toClientId() = ClientId(this)

@JvmInline
value class ExternalSenderKey(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toExternalSenderKey() = ExternalSenderKey(this)

@JvmInline
value class Welcome(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toWelcome() = Welcome(this)

@JvmInline
value class MlsMessage(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toMlsMessage() = MlsMessage(this)

@JvmInline
value class AvsSecret(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toAvsSecret() = AvsSecret(this)

@JvmInline
value class PlaintextMessage(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun String.toPlaintextMessage() = PlaintextMessage(toByteArray())

@JvmInline
value class SignaturePublicKey(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toSignaturePublicKey() = SignaturePublicKey(this)

@JvmInline
value class MLSKeyPackage(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toMLSKeyPackage() = MLSKeyPackage(this)

@JvmInline
value class MLSKeyPackageRef(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

@JvmInline
value class ProposalRef(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toProposalRef() = ProposalRef(this)

@JvmInline
value class ExternallyGeneratedHandle(override val value: List) :
    FfiType, List> {
    override fun lower(): List = value

    override fun toString() = value.joinToString("") { it.toString() }
}

fun List.toExternallyGeneratedHandle() = ExternallyGeneratedHandle(map { it })

@JvmInline
value class CrlDistributionPoints(override val value: Set) :
    FfiType, List> {
    override fun lower(): List = value.asSequence().map { it.toString() }.toList()

    override fun toString() = value.joinToString(", ") { it.toString() }
}

fun List.toCrlDistributionPoint() = CrlDistributionPoints(asSequence().map { java.net.URI(it) }.toSet())


@JvmInline
value class GroupInfo(override val value: ByteArray) : Uniffi {
    override fun toString() = value.toHex()
}

fun ByteArray.toGroupInfo() = GroupInfo(this)

data class GroupInfoBundle(
    val encryptionType: MlsGroupInfoEncryptionType,
    val ratchetTreeType: MlsRatchetTreeType,
    val payload: GroupInfo,
)

fun com.wire.crypto.GroupInfoBundle.lift() =
    GroupInfoBundle(encryptionType, ratchetTreeType, payload.toGroupInfo())

data class CommitBundle(
    /**
     * TLS serialized commit wrapped in a MLS message
     */
    val commit: MlsMessage,
    /**
     * TLS serialized welcome NOT wrapped in a MLS message
     */
    val welcome: Welcome?,
    /**
     * TLS serialized GroupInfo NOT wrapped in a MLS message
     */
    val groupInfoBundle: GroupInfoBundle,
    /**
     * New CRL distribution points that appeared by the introduction of a new credential
     */
    val crlNewDistributionPoints: CrlDistributionPoints?,
)

fun com.wire.crypto.CommitBundle.lift() =
    CommitBundle(commit.toMlsMessage(), welcome?.toWelcome(), groupInfo.lift(), null)

fun com.wire.crypto.ConversationInitBundle.lift() =
    CommitBundle(commit.toMlsMessage(), null, groupInfo.lift(), crlNewDistributionPoints?.toCrlDistributionPoint())

fun com.wire.crypto.MemberAddedMessages.lift() =
    CommitBundle(
        commit.toMlsMessage(),
        welcome.toWelcome(),
        groupInfo.lift(),
        crlNewDistributionPoints?.toCrlDistributionPoint()
    )

/**
 * Returned when a Proposal is created. Helps roll backing a local proposal
 */
data class ProposalBundle(
    /**
     * The proposal message to send to the DS
     */
    val proposal: MlsMessage,
    /**
     * A unique identifier of the proposal to rollback it later if required with [MlsClient.clearPendingProposal]
     */
    val proposalRef: ProposalRef,
    /**
     * New CRL distribution points that appeared by the introduction of a new credential
     */
    val crlNewDistributionPoints: CrlDistributionPoints?,
)

fun com.wire.crypto.ProposalBundle.lift() =
    ProposalBundle(
        proposal.toMlsMessage(),
        proposalRef.toProposalRef(),
        crlNewDistributionPoints?.toCrlDistributionPoint()
    )

/**
 * Contains everything client needs to know after decrypting an (encrypted) Welcome message
 */
data class WelcomeBundle(
    /**
     * MLS Group Id
     */
    val id: MLSGroupId,
    /**
     * New CRL distribution points that appeared by the introduction of a new credential
     */
    val crlNewDistributionPoints: CrlDistributionPoints?,
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as WelcomeBundle

        if (id != other.id) return false
        if (crlNewDistributionPoints != other.crlNewDistributionPoints) return false

        return true
    }

    override fun hashCode(): Int {
        var result = id.hashCode()
        result = 31 * result + (crlNewDistributionPoints?.hashCode() ?: 0)
        return result
    }
}

fun com.wire.crypto.WelcomeBundle.lift() =
    WelcomeBundle(id.toGroupId(), crlNewDistributionPoints?.toCrlDistributionPoint())

/**
 * Represents the potential items a consumer might require after passing us an encrypted message we have decrypted for him
 */
data class DecryptedMessage(
    /**
     * Decrypted text message
     */
    val message: ByteArray?,
    /**
     * Only when decrypted message is a commit, CoreCrypto will renew local proposal which could not make it in the commit.
     * This will contain either:
     *   - local pending proposal not in the accepted commit
     *   - If there is a pending commit, its proposals which are not in the accepted commit
     */
    val proposals: Set,
    /**
     * Is the conversation still active after receiving this commit aka has the user been removed from the group
     */
    val isActive: Boolean,
    /**
     * Delay time in seconds to feed caller timer for committing
     */
    val commitDelay: Long?,
    /**
     * [ClientId] of the sender of the message being decrypted. Only present for application messages.
     */
    val senderClientId: ClientId?,
    /**
     * Is the epoch changed after decrypting this message
     */
    val hasEpochChanged: Boolean,
    /**
     * Identity claims present in the sender credential
     * Only present when the credential is a x509 certificate
     * Present for all messages
     */
    val identity: WireIdentity,
    /**
     * Identity claims present in the sender credential
     * Only present when the credential is a x509 certificate
     * Present for all messages
     */
    val bufferedMessages: List?,
    /**
     * New CRL distribution points that appeared by the introduction of a new credential
     */
    val crlNewDistributionPoints: CrlDistributionPoints?,
) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as DecryptedMessage

        if (message != null) {
            if (other.message == null) return false
            if (!message.contentEquals(other.message)) return false
        } else if (other.message != null) return false
        if (proposals != other.proposals) return false
        if (isActive != other.isActive) return false
        if (commitDelay != other.commitDelay) return false
        if (senderClientId != other.senderClientId) return false
        if (hasEpochChanged != other.hasEpochChanged) return false
        if (identity != other.identity) return false
        if (crlNewDistributionPoints != other.crlNewDistributionPoints) return false

        return true
    }

    override fun hashCode(): Int {
        var result = message?.contentHashCode() ?: 0
        result = 31 * result + proposals.hashCode()
        result = 31 * result + isActive.hashCode()
        result = 31 * result + (commitDelay?.hashCode() ?: 0)
        result = 31 * result + (senderClientId?.hashCode() ?: 0)
        result = 31 * result + hasEpochChanged.hashCode()
        result = 31 * result + identity.hashCode()
        result = 31 * result + (crlNewDistributionPoints?.hashCode() ?: 0)
        return result
    }
}

fun com.wire.crypto.DecryptedMessage.lift() = DecryptedMessage(
    message,
    proposals.asSequence().map { it.lift() }.toSet(),
    isActive,
    commitDelay?.toLong(),
    senderClientId?.toClientId(),
    hasEpochChanged,
    identity.lift(),
    bufferedMessages?.map { it.lift() },
    crlNewDistributionPoints?.toCrlDistributionPoint()
)

/**
 * Type safe recursion of [DecryptedMessage]
 */
data class BufferedDecryptedMessage(
    /** @see DecryptedMessage.message */
    val message: ByteArray?,
    /** @see DecryptedMessage.proposals */
    val proposals: Set,
    /** @see DecryptedMessage.isActive */
    val isActive: Boolean,
    /** @see DecryptedMessage.commitDelay */
    val commitDelay: Long?,
    /** @see DecryptedMessage.senderClientId */
    val senderClientId: ClientId?,
    /** @see DecryptedMessage.hasEpochChanged */
    val hasEpochChanged: Boolean,
    /** @see DecryptedMessage.identity */
    val identity: WireIdentity,
    /** @see DecryptedMessage.crlNewDistributionPoints */
    val crlNewDistributionPoints: CrlDistributionPoints?,
) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as DecryptedMessage

        if (message != null) {
            if (other.message == null) return false
            if (!message.contentEquals(other.message)) return false
        } else if (other.message != null) return false
        if (proposals != other.proposals) return false
        if (isActive != other.isActive) return false
        if (commitDelay != other.commitDelay) return false
        if (senderClientId != other.senderClientId) return false
        if (hasEpochChanged != other.hasEpochChanged) return false
        if (identity != other.identity) return false
        if (crlNewDistributionPoints != other.crlNewDistributionPoints) return false

        return true
    }

    override fun hashCode(): Int {
        var result = message?.contentHashCode() ?: 0
        result = 31 * result + proposals.hashCode()
        result = 31 * result + isActive.hashCode()
        result = 31 * result + (commitDelay?.hashCode() ?: 0)
        result = 31 * result + (senderClientId?.hashCode() ?: 0)
        result = 31 * result + hasEpochChanged.hashCode()
        result = 31 * result + identity.hashCode()
        result = 31 * result + (crlNewDistributionPoints?.hashCode() ?: 0)
        return result
    }
}

fun com.wire.crypto.BufferedDecryptedMessage.lift() = BufferedDecryptedMessage(
    message,
    proposals.asSequence().map { it.lift() }.toSet(),
    isActive,
    commitDelay?.toLong(),
    senderClientId?.toClientId(),
    hasEpochChanged,
    identity.lift(),
    crlNewDistributionPoints?.toCrlDistributionPoint()
)

/**
 * Represents a client using Wire's end-to-end identity solution
 */
data class WireIdentity(
    /**
     * Unique client identifier e.g. `T4Coy4vdRzianwfOgXpn6A:[email protected]`
     */
    val clientId: String,
    /**
     * Status of the Credential at the moment T when this object is created
     */
    val status: DeviceStatus,
    /**
     * MLS thumbprint
     */
    val thumbprint: String,
    /**
     * Indicates whether the credential is Basic or X509
     */
    val credentialType: CredentialType,
    /**
     * In case [credentialType] is [CredentialType.X509] this is populated
     */
    val x509Identity: X509Identity?,
)

fun com.wire.crypto.WireIdentity.lift() =
    WireIdentity(clientId, status.lift(), thumbprint, credentialType.lift(), x509Identity?.lift())

/**
 * Represents the parts of WireIdentity that are specific to a X509 certificate (and not a Basic one).
 */
data class X509Identity(
    /**
     * user handle e.g. `john_wire`
     */
    val handle: String,
    /**
     * Name as displayed in the messaging application e.g. `John Fitzgerald Kennedy`
     */
    val displayName: String,
    /**
     * DNS domain for which this identity proof was generated e.g. `whitehouse.gov`
     */
    val domain: String,
    /**
     * X509 certificate identifying this client in the MLS group ; PEM encoded
     */
    val certificate: String,
    /**
     * X509 certificate serial number
     */
    val serialNumber: String,
    /**
     * X509 certificate not before as Unix timestamp
     */
    val notBefore: java.time.Instant,
    /**
     * X509 certificate not after as Unix timestamp
     */
    val notAfter: java.time.Instant,
)

fun com.wire.crypto.X509Identity.lift() =
    X509Identity(
        handle,
        displayName,
        domain,
        certificate,
        serialNumber,
        java.time.Instant.ofEpochSecond(notBefore.toLong()),
        java.time.Instant.ofEpochSecond(notAfter.toLong())
    )

/**
 * Indicates the standalone status of a device Credential in a MLS group at a moment T. This does not represent the
 * states where a device is not using MLS or is not using end-to-end identity
 */
enum class DeviceStatus {
    /**
     * All is fine
     */
    Valid,

    /**
     * The Credential's certificate is expired
     */
    Expired,

    /**
     * The Credential's certificate is revoked (not implemented yet)
     */
    Revoked,
}

fun com.wire.crypto.DeviceStatus.lift(): DeviceStatus = when (this) {
    com.wire.crypto.DeviceStatus.VALID -> DeviceStatus.Valid
    com.wire.crypto.DeviceStatus.EXPIRED -> DeviceStatus.Expired
    com.wire.crypto.DeviceStatus.REVOKED -> DeviceStatus.Revoked
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy