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.
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),}
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.
Key packages contain a credential to identify the client:
use openmls::prelude::*;// Create key package with credentiallet key_package = KeyPackage::builder() .build( ciphersuite, &provider, &signature_keys, credential_with_key, )?;// The credential is now embedded in the key package's leaf nodelet embedded_credential = key_package.leaf_node().credential();
When creating or joining a group, you provide a credential:
use openmls::prelude::*;// Create a group with a credentiallet mut group = MlsGroup::new( &provider, &signer, &MlsGroupCreateConfig::default(), credential_with_key,)?;// Each member's credential is stored in their leaf nodelet members = group.members().collect::<Vec<_>>();for member in members { let member_credential = &member.credential; // Validate member_credential with your Authentication Service}
Members can update their credential by sending an Update proposal:
use openmls::prelude::*;// Create a new credential with different identitylet new_identity = b"alice-new@example.com".to_vec();let new_credential = BasicCredential::new(new_identity);// Generate new signature keyslet 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 keylet (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.
Applications must validate credentials using their Authentication Service:
use openmls::prelude::*;// Process an incoming messagelet 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)?; } _ => {}}
Clients can use the same credential across multiple groups:
let credential_with_key = /* ... */;// Use in first grouplet 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(),)?;
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 credentiallet work_group = MlsGroup::new( &provider, &signer1, &MlsGroupCreateConfig::default(), credential_with_key1,)?;// Personal group uses personal credentiallet personal_group = MlsGroup::new( &provider, &signer2, &MlsGroupCreateConfig::default(), credential_with_key2,)?;
use openmls::prelude::*;// Get credential from a key packagelet credential = key_package.leaf_node().credential();// Convert to BasicCredential if it's the Basic typeif 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));}