Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/openmls/openmls/llms.txt

Use this file to discover all available pages before exploring further.

Credentials encode the identity of clients in MLS groups. They authenticate messages and are embedded in leaf nodes to represent group members.

What are credentials?

A credential contains identifying information about the client that created it. In MLS:
  • Every leaf node in the group tree contains a credential
  • Every key package contains a credential
  • Credentials are used to authenticate messages from group members
  • The binding between credentials and clients is validated by an Authentication Service
The implementation of the Authentication Service and how it validates credentials is not specified by MLS. Applications must implement their own authentication logic.

Credential types

OpenMLS defines several credential types:
pub enum CredentialType {
    /// A BasicCredential
    Basic = 1,
    /// An X.509 Certificate  
    X509 = 2,
    /// A GREASE credential type for ensuring extensibility
    Grease(u16),
    /// Another type of credential not in the MLS protocol spec
    Other(u16),
}

BasicCredential

OpenMLS currently only supports BasicCredential, which consists of an identity as an opaque byte string:
pub struct BasicCredential {
    identity: VLBytes,
}
The BasicCredential type:
  • Contains only an identity field (no key material)
  • Does not define how the identity relates to signature keys
  • Leaves identity validation to the application’s Authentication Service
The link between a BasicCredential and its signature keys is not defined by MLS. Applications must ensure this binding is authenticated by their Authentication Service.

Creating credentials

To create a BasicCredential, provide an identity as bytes:
use openmls::prelude::*;

let identity = b"alice@example.com";
let credential = BasicCredential::new(identity.to_vec());

// Convert to the generic Credential type
let credential: Credential = credential.into();

Creating credentials with signature keys

Credentials are typically used together with signature keys. OpenMLS provides a reference implementation in the openmls_basic_credential crate:
use openmls::prelude::*;
use openmls_basic_credential::SignatureKeyPair;
use openmls_rust_crypto::OpenMlsRustCrypto;

let provider = OpenMlsRustCrypto::default();
let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;

// Create a credential
let identity = b"alice@example.com".to_vec();
let credential = BasicCredential::new(identity);

// Generate signature keys
let signature_keys = SignatureKeyPair::new(
    ciphersuite.signature_algorithm()
)?;

// Store the private key
signature_keys.store(provider.storage())?;

// Bundle credential with public key
let credential_with_key = CredentialWithKey {
    credential: credential.into(),
    signature_key: signature_keys.public().into(),
};

CredentialWithKey

OpenMLS uses CredentialWithKey to bundle a credential with its corresponding signature public key:
pub struct CredentialWithKey {
    pub credential: Credential,
    pub signature_key: SignaturePublicKey,
}
This structure:
  • Pairs a credential with its signature verification key
  • Is embedded in leaf nodes and key packages
  • Ensures the credential and key are always used together

Using credentials in key packages

Key packages contain a credential to identify the client:
use openmls::prelude::*;

// Create key package with credential
let key_package = KeyPackage::builder()
    .build(
        ciphersuite,
        &provider,
        &signature_keys,
        credential_with_key,
    )?;

// The credential is now embedded in the key package's leaf node
let embedded_credential = key_package.leaf_node().credential();

Credentials in groups

When creating or joining a group, you provide a credential:
use openmls::prelude::*;

// Create a group with a credential
let mut group = MlsGroup::new(
    &provider,
    &signer,
    &MlsGroupCreateConfig::default(),
    credential_with_key,
)?;

// Each member's credential is stored in their leaf node
let members = group.members().collect::<Vec<_>>();
for member in members {
    let member_credential = &member.credential;
    // Validate member_credential with your Authentication Service
}

Credential updates

Members can update their credential by sending an Update proposal:
use openmls::prelude::*;

// Create a new credential with different identity
let new_identity = b"alice-new@example.com".to_vec();
let new_credential = BasicCredential::new(new_identity);

// Generate new signature keys
let new_signature_keys = SignatureKeyPair::new(
    ciphersuite.signature_algorithm()
)?;
new_signature_keys.store(provider.storage())?;

let new_credential_with_key = CredentialWithKey {
    credential: new_credential.into(),
    signature_key: new_signature_keys.public().into(),
};

// Update must be authenticated with OLD signature key
let (message, welcome, group_info) = group.self_update(
    &provider,
    &old_signer, // Use old signer to authenticate the update
    Some(new_credential_with_key.clone()),
)?;

// After merging, future messages use new_signature_keys
When a member updates their credential, the update must be authenticated using the signature key corresponding to their old credential. Applications must verify with their Authentication Service that the new credential is valid.

Credential validation

Applications must validate credentials using their Authentication Service:
use openmls::prelude::*;

// Process an incoming message
let unverified_message = group.parse_message(message_in, &provider)?;

let processed_message = group.process_unverified_message(
    unverified_message,
    None, // No custom proposal validation
    &provider,
)?;

match processed_message {
    ProcessedMessage::StagedCommitMessage(staged_commit) => {
        // Check if any member credentials changed
        for add in staged_commit.add_proposals() {
            let new_member_credential = add.key_package().leaf_node().credential();
            
            // Validate with your Authentication Service
            if !authentication_service.validate(new_member_credential).await? {
                // Reject the commit
                return Err("Invalid credential");
            }
        }
        
        // Merge if validation passed
        group.merge_staged_commit(&provider, *staged_commit)?;
    }
    _ => {}
}

Validation workflow

1

Receive message with credential

Extract the credential from a key package, leaf node, or update proposal.
2

Verify cryptographic binding

OpenMLS verifies that messages are signed with the key corresponding to the credential.
3

Validate with Authentication Service

The application queries its Authentication Service to verify the identity claim.
4

Accept or reject

Based on the validation result, accept (merge) or reject the message.

Credential lifecycle

Same credential, multiple groups

Clients can use the same credential across multiple groups:
let credential_with_key = /* ... */;

// Use in first group
let group1 = MlsGroup::new(
    &provider,
    &signer,
    &MlsGroupCreateConfig::default(),
    credential_with_key.clone(),
)?;

// Reuse in second group  
let group2 = MlsGroup::new(
    &provider,
    &signer,
    &MlsGroupCreateConfig::default(),
    credential_with_key.clone(),
)?;

Distinct credentials per group

Alternatively, clients can use different credentials for different groups:
let credential1 = BasicCredential::new(b"alice-work".to_vec());
let credential2 = BasicCredential::new(b"alice-personal".to_vec());

// Work group uses work credential
let work_group = MlsGroup::new(
    &provider,
    &signer1,
    &MlsGroupCreateConfig::default(),
    credential_with_key1,
)?;

// Personal group uses personal credential
let personal_group = MlsGroup::new(
    &provider,
    &signer2,
    &MlsGroupCreateConfig::default(),
    credential_with_key2,
)?;

Reading credential data

Extract identity information from credentials:
use openmls::prelude::*;

// Get credential from a key package
let credential = key_package.leaf_node().credential();

// Convert to BasicCredential if it's the Basic type
if credential.credential_type() == CredentialType::Basic {
    let basic_credential = BasicCredential::try_from(credential.clone())?;
    let identity = basic_credential.identity();
    
    // Use the identity
    println!("Identity: {}", String::from_utf8_lossy(identity));
}

Custom credentials

While OpenMLS currently only supports BasicCredential, the Credential type can store custom credential types:
use openmls::prelude::*;
use tls_codec::{TlsSerialize, TlsDeserialize, TlsSize};

#[derive(TlsSerialize, TlsDeserialize, TlsSize)]
struct CustomCredential {
    custom_field1: u32,
    custom_field2: Vec<u8>,
}

// Serialize custom credential
let custom = CustomCredential {
    custom_field1: 42,
    custom_field2: vec![1, 2, 3],
};

let serialized = custom.tls_serialize_detached()?;

// Create generic Credential with custom type
let credential = Credential::new(
    CredentialType::Other(1234),
    serialized,
);
Custom credentials must be serialized as variable-length vectors for compatibility with OpenMLS’s credential handling.

Key packages

Learn how credentials are embedded in key packages

Groups

Understand how credentials represent members in groups

Build docs developers (and LLMs) love