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.

Credential validation is a critical security operation that allows group members to verify the authenticity and authorization of other members’ credentials. Proper validation prevents unauthorized access and impersonation attacks.

Why validate credentials?

Credential validation is essential for:
  • Authentication - Verify the identity of group members
  • Authorization - Ensure members are allowed to join
  • Trust establishment - Validate credential signatures and chains
  • Security compliance - Meet organizational security requirements
Security Critical: OpenMLS performs cryptographic validation but cannot verify the semantic meaning or authorization of credentials. Applications MUST implement their own credential validation logic based on business requirements.

When to validate

The MLS protocol specification requires credential validation in two main scenarios:

When joining a group

Validate all existing members when processing a Welcome message:
use openmls::prelude::*;

let staged_welcome = processed_welcome.into_staged_welcome(
    provider,
    ratchet_tree,
)?;

// Validate the welcome sender
let sender = staged_welcome.welcome_sender()?;
let sender_credential = sender.credential();

if !is_credential_valid(sender_credential) {
    return Err("Invalid welcome sender credential");
}

// Validate all group members
for member in staged_welcome.members() {
    if !is_credential_valid(&member.credential) {
        return Err("Invalid member credential");
    }
}

// Only join if all credentials are valid
let bob_group = staged_welcome.into_group(provider)?;

When processing messages

Validate credentials in add and update proposals:
use openmls::prelude::*;

let processed = alice_group.process_message(provider, message)?;

if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = processed.into_content() {
    // Validate add proposals
    for add_proposal in staged_commit.add_proposals() {
        let key_package = add_proposal.add_proposal().key_package();
        let credential = key_package.leaf_node().credential();
        
        if !is_credential_valid(credential) {
            return Err("Cannot accept: invalid credential in add proposal");
        }
    }
    
    // Validate update proposals
    for update_proposal in staged_commit.update_proposals() {
        let leaf_node = update_proposal.update_proposal().leaf_node();
        let credential = leaf_node.credential();
        
        if !is_credential_valid(credential) {
            return Err("Cannot accept: invalid credential in update proposal");
        }
    }
    
    // Only merge if all credentials are valid
    alice_group.merge_staged_commit(provider, *staged_commit)?;
}

Basic credential validation

Basic credentials contain an identity and signature key:
use openmls::prelude::*;

fn validate_basic_credential(credential: &Credential) -> Result<bool, Error> {
    match credential.credential_type() {
        CredentialType::Basic => {
            let basic = credential.as_basic_credential()?;
            let identity = basic.identity();
            
            // Validate identity format
            if identity.is_empty() {
                return Ok(false);
            }
            
            // Check against allowed identities
            let allowed_identities = get_allowed_identities();
            if !allowed_identities.contains(identity) {
                return Ok(false);
            }
            
            Ok(true)
        }
        _ => Ok(false), // Reject unknown credential types
    }
}

Certificate-based validation

For X.509 credentials, validate the certificate chain:
use openmls::prelude::*;

fn validate_x509_credential(
    credential: &Credential,
    trusted_roots: &[Certificate],
) -> Result<bool, Error> {
    match credential.credential_type() {
        CredentialType::X509 => {
            let x509 = credential.as_x509_credential()?;
            let cert_chain = x509.certificates();
            
            // Verify certificate chain
            if !verify_chain(cert_chain, trusted_roots)? {
                return Ok(false);
            }
            
            // Check certificate validity period
            let cert = &cert_chain[0];
            if !cert.is_currently_valid()? {
                return Ok(false);
            }
            
            // Check certificate revocation
            if is_revoked(cert)? {
                return Ok(false);
            }
            
            Ok(true)
        }
        _ => Ok(false),
    }
}

Validation workflow

1
Extract the credential
2
Get the credential from the leaf node or key package:
3
use openmls::prelude::*;

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

// From a staged welcome
let sender = staged_welcome.welcome_sender()?;
let credential = sender.credential();

// From a member
for member in alice_group.members() {
    let credential = &member.credential;
}
4
Check credential type
5
Verify the credential type is supported:
6
let credential_type = credential.credential_type();

match credential_type {
    CredentialType::Basic => {
        // Handle basic credential
    }
    CredentialType::X509 => {
        // Handle X.509 certificate
    }
    _ => {
        return Err("Unsupported credential type");
    }
}
7
Verify identity
8
Validate the identity matches expectations:
9
let basic_credential = credential.as_basic_credential()?;
let identity = basic_credential.identity();

// Parse identity (e.g., email, user ID)
let user_id = parse_identity(identity)?;

// Check authorization
if !is_authorized(user_id) {
    return Err("User not authorized");
}
10
Validate signature key
11
Ensure the signature key is trusted:
12
// Get the signature key from CredentialWithKey
let credential_with_key = &member.credential_with_key;
let signature_key = credential_with_key.signature_key;

// Verify key is in trusted set
if !is_key_trusted(&signature_key) {
    return Err("Signature key not trusted");
}
13
Check external validation service
14
Query an external service for authorization:
15
let identity = basic_credential.identity();

// Call authorization service
let response = authorization_service
    .validate_member(identity)
    .await?;

if !response.is_authorized {
    return Err("Authorization service rejected member");
}

Comprehensive validation example

Implement a complete validation function:
use openmls::prelude::*;

struct CredentialValidator {
    allowed_domains: Vec<String>,
    trusted_roots: Vec<Certificate>,
    revoked_keys: HashSet<Vec<u8>>,
}

impl CredentialValidator {
    fn validate(&self, credential: &Credential) -> Result<(), ValidationError> {
        // Check credential type
        match credential.credential_type() {
            CredentialType::Basic => self.validate_basic(credential)?,
            CredentialType::X509 => self.validate_x509(credential)?,
            _ => return Err(ValidationError::UnsupportedType),
        }
        
        Ok(())
    }
    
    fn validate_basic(&self, credential: &Credential) -> Result<(), ValidationError> {
        let basic = credential.as_basic_credential()
            .map_err(|_| ValidationError::InvalidFormat)?;
        
        let identity = basic.identity();
        
        // Parse as email
        let email = std::str::from_utf8(identity)
            .map_err(|_| ValidationError::InvalidIdentity)?;
        
        // Check domain
        let domain = email.split('@').nth(1)
            .ok_or(ValidationError::InvalidIdentity)?;
        
        if !self.allowed_domains.contains(&domain.to_string()) {
            return Err(ValidationError::UnauthorizedDomain);
        }
        
        Ok(())
    }
    
    fn validate_x509(&self, credential: &Credential) -> Result<(), ValidationError> {
        // Implement X.509 validation
        todo!("X.509 validation")
    }
}

// Usage
let validator = CredentialValidator {
    allowed_domains: vec!["example.com".to_string()],
    trusted_roots: vec![],
    revoked_keys: HashSet::new(),
};

// Validate before merging commit
for add_proposal in staged_commit.add_proposals() {
    let credential = add_proposal
        .add_proposal()
        .key_package()
        .leaf_node()
        .credential();
    
    validator.validate(credential)?;
}

Validation in different contexts

Validating external proposals

External proposals require additional checks:
use openmls::prelude::*;

if let ProcessedMessageContent::ProposalMessage(proposal) = processed.into_content() {
    if matches!(processed.sender(), Sender::External(_)) {
        // Validate external sender is authorized
        let external_senders = alice_group
            .group()
            .group_context()
            .extensions()
            .external_senders()
            .ok_or("No external senders configured")?;
        
        let sender_credential = processed.credential();
        
        let is_authorized = external_senders
            .iter()
            .any(|sender| {
                sender.credential() == sender_credential
            });
        
        if !is_authorized {
            return Err("External sender not authorized");
        }
    }
}

Validating on group creation

Validate your own credential before creating a group:
use openmls::prelude::*;

// Validate own credential
validator.validate(&alice_credential.credential)?;

// Create group
let alice_group = MlsGroup::new(
    provider,
    &alice_signature_keys,
    &config,
    alice_credential,
)?;

Integration with identity providers

Integrate with existing identity systems:
use openmls::prelude::*;

async fn validate_with_idp(
    credential: &Credential,
    idp_client: &IdentityProviderClient,
) -> Result<(), Error> {
    let basic = credential.as_basic_credential()?;
    let identity = basic.identity();
    
    // Query identity provider
    let user_info = idp_client
        .get_user(identity)
        .await?;
    
    // Check user status
    if !user_info.is_active {
        return Err(Error::UserInactive);
    }
    
    // Check group membership permissions
    if !user_info.has_permission("mls.group.join") {
        return Err(Error::InsufficientPermissions);
    }
    
    Ok(())
}

Validation errors

Define clear validation errors:
#[derive(Debug)]
enum ValidationError {
    UnsupportedType,
    InvalidFormat,
    InvalidIdentity,
    UnauthorizedDomain,
    ExpiredCertificate,
    RevokedCertificate,
    UntrustedRoot,
    InsufficientPermissions,
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            ValidationError::UnsupportedType => {
                write!(f, "Credential type not supported")
            }
            ValidationError::UnauthorizedDomain => {
                write!(f, "Domain not in allowed list")
            }
            // ... other cases
            _ => write!(f, "Validation failed"),
        }
    }
}

Best practices

Always validate before merging: Never merge a commit without validating all credentials in add and update proposals.Validate early: Check credentials as soon as possible to fail fast.Log validation failures: Record validation failures for security monitoring.Use allowlists: Prefer allowlists over denylists for domain/identity validation.

Security considerations

  • Credential replay: Validate that credentials are current and not reused
  • Revocation checking: Implement certificate revocation checks for X.509
  • Time validation: Check that certificates are within their validity period
  • Chain validation: Verify complete certificate chains to trusted roots
  • Authorization: Validate not just identity but authorization to join
  • Credential - Credential containing identity information
  • CredentialType - Type of credential (Basic, X.509, etc.)
  • BasicCredential - Simple identity-based credential
  • CredentialWithKey - Credential bundled with signature key

Next steps

Processing messages

Learn where to validate credentials when processing

Managing members

Understand member operations requiring validation