core_crypto/mls/conversation/
welcome.rsuse std::borrow::BorrowMut;
use super::{Error, Result};
use crate::{
LeafError, MlsError, RecursiveError,
context::CentralContext,
e2e_identity::init_certificates::NewCrlDistributionPoint,
group_store::GroupStore,
mls::credential::crl::{extract_crl_uris_from_group, get_new_crl_distribution_points},
prelude::{ConversationId, MlsConversation, MlsConversationConfiguration, MlsCustomConfiguration},
};
use core_crypto_keystore::{connection::FetchFromDatabase, entities::PersistedMlsPendingGroup};
use mls_crypto_provider::MlsCryptoProvider;
use openmls::prelude::{MlsGroup, MlsMessageIn, MlsMessageInBody, Welcome};
use openmls_traits::OpenMlsCryptoProvider;
use tls_codec::Deserialize;
#[derive(Debug)]
pub struct WelcomeBundle {
pub id: ConversationId,
pub crl_new_distribution_points: NewCrlDistributionPoint,
}
impl CentralContext {
#[cfg_attr(test, crate::dispotent)]
pub async fn process_raw_welcome_message(
&self,
welcome: Vec<u8>,
custom_cfg: MlsCustomConfiguration,
) -> Result<WelcomeBundle> {
let mut cursor = std::io::Cursor::new(welcome);
let welcome =
MlsMessageIn::tls_deserialize(&mut cursor).map_err(Error::tls_deserialize("mls message in (welcome)"))?;
self.process_welcome_message(welcome, custom_cfg).await
}
#[cfg_attr(test, crate::dispotent)]
pub async fn process_welcome_message(
&self,
welcome: MlsMessageIn,
custom_cfg: MlsCustomConfiguration,
) -> Result<WelcomeBundle> {
let MlsMessageInBody::Welcome(welcome) = welcome.extract() else {
return Err(Error::CallerError(
"the message provided to process_welcome_message was not a welcome message",
));
};
let cs = welcome.ciphersuite().into();
let configuration = MlsConversationConfiguration {
ciphersuite: cs,
custom: custom_cfg,
..Default::default()
};
let mls_provider = self
.mls_provider()
.await
.map_err(RecursiveError::root("getting mls provider"))?;
let mut mls_groups = self
.mls_groups()
.await
.map_err(RecursiveError::root("getting mls groups"))?;
let conversation =
MlsConversation::from_welcome_message(welcome, configuration, &mls_provider, mls_groups.borrow_mut())
.await?;
let crl_new_distribution_points = get_new_crl_distribution_points(
&mls_provider,
extract_crl_uris_from_group(&conversation.group)
.map_err(RecursiveError::mls_credential("extracting crl uris from group"))?,
)
.await
.map_err(RecursiveError::mls_credential("getting new crl distribution points"))?;
let id = conversation.id.clone();
mls_groups.insert(id.clone(), conversation);
Ok(WelcomeBundle {
id,
crl_new_distribution_points,
})
}
}
impl MlsConversation {
async fn from_welcome_message(
welcome: Welcome,
configuration: MlsConversationConfiguration,
backend: &MlsCryptoProvider,
mls_groups: &mut GroupStore<MlsConversation>,
) -> Result<Self> {
let mls_group_config = configuration.as_openmls_default_configuration()?;
let group = MlsGroup::new_from_welcome(backend, &mls_group_config, welcome, None).await;
let group = match group {
Err(openmls::prelude::WelcomeError::NoMatchingKeyPackage)
| Err(openmls::prelude::WelcomeError::NoMatchingEncryptionKey) => return Err(Error::OrphanWelcome),
_ => group.map_err(MlsError::wrap("group could not be created from welcome"))?,
};
let id = ConversationId::from(group.group_id().as_slice());
let existing_conversation = mls_groups.get_fetch(&id[..], &backend.keystore(), None).await;
let conversation_exists = existing_conversation.ok().flatten().is_some();
let pending_group = backend.key_store().find::<PersistedMlsPendingGroup>(&id[..]).await;
let pending_group_exists = pending_group.ok().flatten().is_some();
if conversation_exists || pending_group_exists {
return Err(LeafError::ConversationAlreadyExists(id).into());
}
Self::from_mls_group(group, configuration, backend).await
}
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
use crate::test_utils::*;
use super::*;
wasm_bindgen_test_configure!(run_in_browser);
#[apply(all_cred_cipher)]
#[wasm_bindgen_test]
async fn joining_from_welcome_should_prune_local_key_material(case: TestCase) {
run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
Box::pin(async move {
let id = conversation_id();
let bob = bob_central.rand_key_package(&case).await;
let prev_count = bob_central.context.count_entities().await;
alice_central
.context
.new_conversation(&id, case.credential_type, case.cfg.clone())
.await
.unwrap();
alice_central
.context
.conversation(&id)
.await
.unwrap()
.add_members(vec![bob])
.await
.unwrap();
let welcome = alice_central.mls_transport.latest_welcome_message().await;
bob_central
.context
.process_welcome_message(welcome.into(), case.custom_cfg())
.await
.unwrap();
let next_count = bob_central.context.count_entities().await;
assert_eq!(next_count.key_package, prev_count.key_package - 1);
assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
})
})
.await;
}
#[apply(all_cred_cipher)]
#[wasm_bindgen_test]
async fn process_welcome_should_fail_when_already_exists(case: TestCase) {
run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
Box::pin(async move {
let id = conversation_id();
alice_central
.context
.new_conversation(&id, case.credential_type, case.cfg.clone())
.await
.unwrap();
let bob = bob_central.rand_key_package(&case).await;
alice_central
.context
.conversation(&id)
.await
.unwrap()
.add_members(vec![bob])
.await
.unwrap();
let welcome = alice_central.mls_transport.latest_welcome_message().await;
bob_central
.context
.new_conversation(&id, case.credential_type, case.cfg.clone())
.await
.unwrap();
let join_welcome = bob_central
.context
.process_welcome_message(welcome.into(), case.custom_cfg())
.await;
assert!(
matches!(join_welcome.unwrap_err(), Error::Leaf(LeafError::ConversationAlreadyExists(i)) if i == id)
);
})
})
.await;
}
}