Skip to main content
Wire’s End-to-End Identity (E2EI) system extends MLS credentials from self-asserted key pairs (Basic) to verifiable X.509 certificates issued by the Wire ACME server. Once enrolled, every message carries a cryptographic proof of the sender’s display name, handle, and domain. The enrollment flow follows the ACME challenge protocol:
  1. Create an enrollment object (e2ei_new_enrollment).
  2. Drive the ACME challenge steps through wire_e2e_identity helpers.
  3. Present the signed certificate chain to CoreCrypto (e2ei_mls_init_only or save_x509_credential).

Enrollment

e2ei_new_enrollment

pub async fn e2ei_new_enrollment(
    &self,
    client_id: ClientId,
    display_name: String,
    handle: String,
    team: Option<String>,
    expiry_sec: u32,
    ciphersuite: Ciphersuite,
) -> Result<E2eiEnrollment>
Creates an E2eiEnrollment instance with freshly generated private key material. The enrollment object is used to drive the ACME challenge flow and must not be shared across threads.
client_id
ClientId
required
The Wire-qualified client identifier, e.g. b7ac11a4-8f01-4527-af88-1c30885a7931:6add501bacd1d90e@example.com.
display_name
String
required
Human-readable display name shown in the UI, e.g. Smith, Alice M (QA).
handle
String
required
User handle, e.g. alice.smith.qa@example.com.
team
Option<String>
Optional team name for the Wire team the user belongs to.
expiry_sec
u32
required
Requested certificate lifetime in seconds. The ACME server may issue a shorter lifetime.
ciphersuite
Ciphersuite
required
Ciphersuite that determines the signature algorithm for the certificate.
E2eiEnrollment
E2eiEnrollment
An enrollment session object. Drive the ACME flow using its methods, then pass it to save_x509_credential or e2ei_mls_init_only.
Example
let tx = cc.new_transaction().await?;
let mut enrollment = tx.e2ei_new_enrollment(
    client_id,
    "Alice Smith".to_string(),
    "alice.smith@wire.com".to_string(),
    Some("Engineering".to_string()),
    90 * 24 * 3600, // 90 days
    Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
).await?;
// ... drive ACME flow using enrollment ...
tx.finish().await?;

e2ei_mls_init_only

pub async fn e2ei_mls_init_only(
    &self,
    enrollment: &mut E2eiEnrollment,
    certificate_chain: String,
    transport: Arc<dyn MlsTransport>,
) -> Result<(CredentialRef, NewCrlDistributionPoints)>
Initialises the MLS session with an X.509 credential in a single step. Use this for fresh client enrollment — it combines certificate parsing, credential creation, and mls_init into one call.
enrollment
&mut E2eiEnrollment
required
A completed enrollment object (ACME flow finished).
certificate_chain
String
required
The PEM-encoded certificate chain returned by the ACME server’s /certificate endpoint.
transport
Arc<dyn MlsTransport>
required
The delivery service transport to attach to the new MLS session.
CredentialRef
CredentialRef
A reference to the newly created X.509 credential.
NewCrlDistributionPoints
NewCrlDistributionPoints
CRL distribution points extracted from the certificate chain. Fetch and register these with e2ei_register_crl.

save_x509_credential

pub async fn save_x509_credential(
    &self,
    enrollment: &mut E2eiEnrollment,
    certificate_chain: String,
) -> Result<(CredentialRef, NewCrlDistributionPoints)>
Saves a new X.509 credential from a completed enrollment. Use this when the MLS session is already initialised (credential rotation) rather than for fresh setup. After saving, you must:
  1. Call guard.set_credential_by_ref(&new_cred_ref) on every conversation.
  2. Generate new key packages with generate_keypackage.
  3. Upload the new key packages to the DS and delete the old ones.
  4. Call remove_credential on the old credential.
enrollment
&mut E2eiEnrollment
required
A completed enrollment object.
certificate_chain
String
required
PEM-encoded certificate chain from the ACME server.
CredentialRef
CredentialRef
A reference to the new credential.
NewCrlDistributionPoints
NewCrlDistributionPoints
New CRL distribution points that should be registered.

PKI environment

e2ei_is_enabled

pub async fn e2ei_is_enabled(
    &self,
    ciphersuite: Ciphersuite,
) -> Result<bool>
Returns true if the current MLS session uses an X.509 credential for the given ciphersuite.
ciphersuite
Ciphersuite
required
The ciphersuite to check.
bool
bool
true when E2EI is active for this ciphersuite.

Conversation state

e2ei_conversation_state

// on ConversationGuard (via Conversation trait)
pub async fn e2ei_conversation_state(
    &self,
) -> Result<E2eiConversationState>
Computes the E2EI state of the conversation based on the credentials currently in the ratchet tree. Does not consider pending proposals or commits.
E2eiConversationState
E2eiConversationState

e2ei_verify_group_state

pub async fn e2ei_verify_group_state(
    &self,
    group_info: VerifiableGroupInfo,
) -> Result<E2eiConversationState>
Verifies the E2EI state of a VerifiableGroupInfo using hardened sender-mode ratchet tree verification. Used to assess the E2EI state before joining a group.

E2eiConversationState

#[repr(u8)]
pub enum E2eiConversationState {
    Verified = 1,
    NotVerified,
    NotEnabled,
}
Verified
variant
All current group members hold a valid, unexpired X.509 E2EI certificate. The conversation displays the verified shield.
NotVerified
variant
At least one member has a Basic credential or an expired X.509 certificate. The conversation is degraded. If all X.509 certificates are expired, this state is returned instead of NotEnabled.
NotEnabled
variant
All members are using Basic credentials. E2EI has not been turned on in this conversation.

Identity queries

get_device_identities

// on ConversationGuard (via Conversation trait)
pub async fn get_device_identities(
    &self,
    device_ids: &[impl Borrow<ClientIdRef> + Sync],
) -> Result<Vec<WireIdentity>>
Returns identity information for the specified device (client) IDs within the conversation. For members with a Basic credential, the x509_identity field will be None.
device_ids
&[ClientIdRef]
required
Non-empty list of client IDs to query. Returns an error if the list is empty.
Vec<WireIdentity>
Vec<WireIdentity>
One WireIdentity per matching member. See WireIdentity below.
Example
let tx = cc.new_transaction().await?;
let guard = tx.conversation(&conv_id).await?;
let ids = guard.get_device_identities(&[alice_id, bob_id]).await?;
for identity in &ids {
    println!("{}: {:?}", identity.client_id, identity.status);
}
tx.finish().await?;

get_user_identities

// on ConversationGuard (via Conversation trait)
pub async fn get_user_identities(
    &self,
    user_ids: &[String],
) -> Result<HashMap<String, Vec<WireIdentity>>>
Returns a map of user ID → list of device identities. A user may have multiple devices, each with its own identity.
user_ids
&[String]
required
Non-empty list of Wire user IDs (UUID strings). Returns an error if the list is empty.
HashMap<String, Vec<WireIdentity>>
HashMap<String, Vec<WireIdentity>>
Keyed by user ID string. Only users with at least one matching device in the group appear in the map.

WireIdentity

pub struct WireIdentity {
    pub client_id: String,
    pub thumbprint: String,
    pub status: DeviceStatus,
    pub credential_type: CredentialType,
    pub x509_identity: Option<X509Identity>,
}
client_id
String
Unique device identifier in Wire qualified format, e.g. T4Coy4vdRzianwfOgXpn6A:6add501bacd1d90e@whitehouse.gov.
thumbprint
String
MLS thumbprint of the client’s credential (hex-encoded hash of the public key).
status
DeviceStatus
Verification status of the device credential at the time of the query. See DeviceStatus.
credential_type
CredentialType
Basic or X509.
x509_identity
Option<X509Identity>
Present only for X.509 credentials. Contains the verified identity claims from the certificate.

DeviceStatus

// from wire_e2e_identity::legacy::device_status
pub enum DeviceStatus {
    Valid,
    Expired,
    Revoked,
}
Valid
variant
The device’s X.509 certificate is valid and has not been revoked.
Expired
variant
The certificate’s NotAfter date is in the past.
Revoked
variant
The certificate appears in a CRL that has been registered with the PKI environment.

CRL registration helpers

CRL distribution points are discovered when new X.509 credentials are added to the group (via commits, welcomes, or enrollment). The NewCrlDistributionPoints value returned by enrollment and commit operations contains URLs that your application must fetch and register.
// After receiving new CRL DPs:
for dp_url in crl_distribution_points {
    let crl_bytes = http_client.get(&dp_url).await?;
    let tx = cc.new_transaction().await?;
    tx.e2ei_register_crl(&dp_url, crl_bytes).await?;
    tx.finish().await?;
}

Build docs developers (and LLMs) love