core_crypto/mls/session/
mod.rs

1pub(crate) mod config;
2pub(crate) mod e2e_identity;
3mod epoch_observer;
4mod error;
5mod history_observer;
6pub(crate) mod id;
7pub(crate) mod identifier;
8pub(crate) mod identities;
9pub(crate) mod key_package;
10pub(crate) mod user_id;
11
12use crate::{
13    CoreCrypto, KeystoreError, LeafError, MlsError, MlsTransport, RecursiveError,
14    group_store::GroupStore,
15    mls::{
16        self, HasSessionAndCrypto,
17        conversation::ImmutableConversation,
18        credential::{CredentialBundle, ext::CredentialExt},
19    },
20    prelude::{
21        CertificateBundle, ClientId, ConversationId, HistorySecret, MlsCiphersuite, MlsCredentialType,
22        config::ValidatedSessionConfig, identifier::ClientIdentifier, key_package::KEYPACKAGE_DEFAULT_LIFETIME,
23    },
24};
25use async_lock::RwLock;
26use core_crypto_keystore::{
27    Connection, CryptoKeystoreError,
28    connection::FetchFromDatabase,
29    entities::{EntityFindParams, MlsCredential, MlsSignatureKeyPair},
30};
31pub use epoch_observer::EpochObserver;
32pub(crate) use error::{Error, Result};
33pub use history_observer::HistoryObserver;
34use identities::Identities;
35use log::debug;
36use mls_crypto_provider::{CryptoKeystore, EntropySeed, MlsCryptoProvider};
37use openmls::prelude::{Credential, CredentialType};
38use openmls_basic_credential::SignatureKeyPair;
39use openmls_traits::{OpenMlsCryptoProvider, crypto::OpenMlsCrypto, types::SignatureScheme};
40use openmls_x509_credential::CertificateKeyPair;
41use std::collections::HashSet;
42use std::ops::Deref;
43use std::sync::Arc;
44use tls_codec::{Deserialize, Serialize};
45
46/// A MLS Session enables a user device to communicate via the MLS protocol.
47///
48/// This closely maps to the `Client` term in [RFC 9720], but we avoid that term to avoid ambiguity;
49/// `Client` is very overloaded with distinct meanings.
50///
51/// There is one `Session` per user per device. A session can contain many MLS groups/conversations.
52///
53/// It is cheap to clone a `Session` because everything heavy is wrapped inside an [Arc].
54///
55/// [RFC 9720]: https://www.rfc-editor.org/rfc/rfc9420.html
56#[derive(Clone, derive_more::Debug)]
57pub struct Session {
58    pub(crate) inner: Arc<RwLock<Option<SessionInner>>>,
59    pub(crate) crypto_provider: MlsCryptoProvider,
60    pub(crate) transport: Arc<RwLock<Option<Arc<dyn MlsTransport + 'static>>>>,
61    #[debug("EpochObserver")]
62    pub(crate) epoch_observer: Arc<RwLock<Option<Arc<dyn EpochObserver + 'static>>>>,
63    #[debug("HistoryObserver")]
64    pub(crate) history_observer: Arc<RwLock<Option<Arc<dyn HistoryObserver + 'static>>>>,
65}
66
67#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
68#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
69impl HasSessionAndCrypto for Session {
70    async fn session(&self) -> mls::Result<Session> {
71        Ok(self.clone())
72    }
73
74    async fn crypto_provider(&self) -> mls::Result<MlsCryptoProvider> {
75        Ok(self.crypto_provider.clone())
76    }
77}
78
79#[derive(Clone, Debug)]
80pub(crate) struct SessionInner {
81    id: ClientId,
82    pub(crate) identities: Identities,
83    keypackage_lifetime: std::time::Duration,
84}
85
86impl Session {
87    /// Creates a new [Session].
88    ///
89    /// ## Errors
90    ///
91    /// Failures in the initialization of the KeyStore can cause errors, such as IO, the same kind
92    /// of errors can happen when the groups are being restored from the KeyStore or even during
93    /// the client initialization (to fetch the identity signature).
94    ///
95    /// Other than that, `MlsError` can be caused by group deserialization or during the initialization of the credentials:
96    ///
97    /// * for x509 Credentials if the certificate chain length is lower than 2
98    /// * for Basic Credentials if the signature key cannot be generated, if the scheme is unsupported
99    ///   or the key generation fails
100    pub async fn try_new(
101        ValidatedSessionConfig {
102            db_connection_type,
103            database_key,
104            client_id,
105            external_entropy,
106            ciphersuites,
107            nb_key_packages,
108        }: ValidatedSessionConfig<'_>,
109    ) -> crate::mls::Result<Self> {
110        // Init backend (crypto + rand + keystore)
111        let key_store = CryptoKeystore::open(db_connection_type, &database_key)
112            .await
113            .map_err(MlsError::wrap("initializing keystore"))?;
114        let mls_backend = MlsCryptoProvider::builder()
115            .key_store(key_store)
116            .entropy_seed_opt(external_entropy)
117            .build();
118
119        // We create the core crypto instance first to enable creating a transaction from it and
120        // doing all subsequent actions inside a single transaction, though it forces us to clone
121        // a few Arcs and locks.
122        let session = Self {
123            crypto_provider: mls_backend.clone(),
124            inner: Default::default(),
125            transport: Arc::new(None.into()),
126            epoch_observer: Arc::new(None.into()),
127            history_observer: Arc::new(None.into()),
128        };
129
130        let cc = CoreCrypto::from(session);
131        let context = cc
132            .new_transaction()
133            .await
134            .map_err(RecursiveError::transaction("starting new transaction"))?;
135
136        if let Some(id) = client_id {
137            cc.mls
138                .init(
139                    ClientIdentifier::Basic(id),
140                    ciphersuites.as_slice(),
141                    &mls_backend,
142                    nb_key_packages,
143                )
144                .await
145                .map_err(RecursiveError::mls_client("initializing mls client"))?
146        }
147
148        context
149            .init_pki_env()
150            .await
151            .map_err(RecursiveError::transaction("initializing pki environment"))?;
152        context
153            .finish()
154            .await
155            .map_err(RecursiveError::transaction("finishing transaction"))?;
156
157        Ok(cc.mls)
158    }
159
160    /// Provide the implementation of functions to communicate with the delivery service
161    /// (see [MlsTransport]).
162    pub async fn provide_transport(&self, transport: Arc<dyn MlsTransport>) {
163        self.transport.write().await.replace(transport);
164    }
165
166    /// Initializes the client.
167    /// If the client's cryptographic material is already stored in the keystore, it loads it
168    /// Otherwise, it is being created.
169    ///
170    /// # Arguments
171    /// * `identifier` - client identifier ; either a [ClientId] or a x509 certificate chain
172    /// * `ciphersuites` - all ciphersuites this client is supposed to support
173    /// * `backend` - the KeyStore and crypto provider to read identities from
174    ///
175    /// # Errors
176    /// KeyStore and OpenMls errors can happen
177    pub async fn init(
178        &self,
179        identifier: ClientIdentifier,
180        ciphersuites: &[MlsCiphersuite],
181        backend: &MlsCryptoProvider,
182        nb_key_packages: usize,
183    ) -> Result<()> {
184        self.ensure_unready().await?;
185        let id = identifier.get_id()?;
186
187        let credentials = backend
188            .key_store()
189            .find_all::<MlsCredential>(EntityFindParams::default())
190            .await
191            .map_err(KeystoreError::wrap("finding all mls credentials"))?;
192
193        let credentials = credentials
194            .into_iter()
195            .filter(|mls_credential| mls_credential.id.as_slice() == id.as_slice())
196            .map(|mls_credential| -> Result<_> {
197                let credential = Credential::tls_deserialize(&mut mls_credential.credential.as_slice())
198                    .map_err(Error::tls_deserialize("mls credential"))?;
199                Ok((credential, mls_credential.created_at))
200            })
201            .collect::<Result<Vec<_>>>()?;
202
203        if credentials.is_empty() {
204            debug!(count = nb_key_packages, ciphersuites:? = ciphersuites; "Generating client");
205            self.generate(identifier, backend, ciphersuites, nb_key_packages)
206                .await?;
207        } else {
208            let signature_schemes = ciphersuites
209                .iter()
210                .map(|cs| cs.signature_algorithm())
211                .collect::<HashSet<_>>();
212            let load_result = self.load(backend, id.as_ref(), credentials, signature_schemes).await;
213            if let Err(Error::ClientSignatureNotFound) = load_result {
214                debug!(count = nb_key_packages, ciphersuites:? = ciphersuites; "Client signature not found. Generating client");
215                self.generate(identifier, backend, ciphersuites, nb_key_packages)
216                    .await?;
217            } else {
218                load_result?;
219            }
220        };
221
222        Ok(())
223    }
224
225    /// Resets the client to an uninitialized state.
226    #[cfg(test)]
227    pub(crate) async fn reset(&self) {
228        let mut inner_lock = self.inner.write().await;
229        *inner_lock = None;
230    }
231
232    pub(crate) async fn is_ready(&self) -> bool {
233        let inner_lock = self.inner.read().await;
234        inner_lock.is_some()
235    }
236
237    async fn ensure_unready(&self) -> Result<()> {
238        if self.is_ready().await {
239            Err(Error::UnexpectedlyReady)
240        } else {
241            Ok(())
242        }
243    }
244
245    async fn replace_inner(&self, new_inner: SessionInner) {
246        let mut inner_lock = self.inner.write().await;
247        *inner_lock = Some(new_inner);
248    }
249
250    /// Get an immutable view of an `MlsConversation`.
251    ///
252    /// Because it operates on the raw conversation type, this may be faster than
253    /// [crate::transaction_context::TransactionContext::conversation]. for transient and immutable
254    /// purposes. For long-lived or mutable purposes, prefer the other method.
255    pub async fn get_raw_conversation(&self, id: &ConversationId) -> Result<ImmutableConversation> {
256        let raw_conversation = GroupStore::fetch_from_keystore(id, &self.crypto_provider.keystore(), None)
257            .await
258            .map_err(RecursiveError::root("getting conversation by id"))?
259            .ok_or_else(|| LeafError::ConversationNotFound(id.clone()))?;
260        Ok(ImmutableConversation::new(raw_conversation, self.clone()))
261    }
262
263    /// Returns the client's most recent public signature key as a buffer.
264    /// Used to upload a public key to the server in order to verify client's messages signature.
265    ///
266    /// # Arguments
267    /// * `ciphersuite` - a callback to be called to perform authorization
268    /// * `credential_type` - of the credential to look for
269    pub async fn public_key(
270        &self,
271        ciphersuite: MlsCiphersuite,
272        credential_type: MlsCredentialType,
273    ) -> crate::mls::Result<Vec<u8>> {
274        let cb = self
275            .find_most_recent_credential_bundle(ciphersuite.signature_algorithm(), credential_type)
276            .await
277            .map_err(RecursiveError::mls_client("finding most recent credential bundle"))?;
278        Ok(cb.signature_key.to_public_vec())
279    }
280
281    pub(crate) fn new_basic_credential_bundle(
282        id: &ClientId,
283        sc: SignatureScheme,
284        backend: &MlsCryptoProvider,
285    ) -> Result<CredentialBundle> {
286        let (sk, pk) = backend
287            .crypto()
288            .signature_key_gen(sc)
289            .map_err(MlsError::wrap("generating a signature key"))?;
290
291        let signature_key = SignatureKeyPair::from_raw(sc, sk, pk);
292        let credential = Credential::new_basic(id.to_vec());
293        let cb = CredentialBundle {
294            credential,
295            signature_key,
296            created_at: 0,
297        };
298
299        Ok(cb)
300    }
301
302    pub(crate) fn new_x509_credential_bundle(cert: CertificateBundle) -> Result<CredentialBundle> {
303        let created_at = cert
304            .get_created_at()
305            .map_err(RecursiveError::mls_credential("getting credetntial created at"))?;
306        let (sk, ..) = cert.private_key.into_parts();
307        let chain = cert.certificate_chain;
308
309        let kp = CertificateKeyPair::new(sk, chain.clone()).map_err(MlsError::wrap("creating certificate key pair"))?;
310
311        let credential = Credential::new_x509(chain).map_err(MlsError::wrap("creating x509 credential"))?;
312
313        let cb = CredentialBundle {
314            credential,
315            signature_key: kp.0,
316            created_at,
317        };
318        Ok(cb)
319    }
320
321    /// Checks if a given conversation id exists locally
322    pub async fn conversation_exists(&self, id: &ConversationId) -> Result<bool> {
323        match self.get_raw_conversation(id).await {
324            Ok(_) => Ok(true),
325            Err(Error::Leaf(LeafError::ConversationNotFound(_))) => Ok(false),
326            Err(e) => Err(e),
327        }
328    }
329
330    /// Generates a random byte array of the specified size
331    pub fn random_bytes(&self, len: usize) -> crate::mls::Result<Vec<u8>> {
332        use openmls_traits::random::OpenMlsRand as _;
333        self.crypto_provider
334            .rand()
335            .random_vec(len)
336            .map_err(MlsError::wrap("generating random vector"))
337            .map_err(Into::into)
338    }
339
340    /// Waits for running transactions to finish, then closes the connection with the local KeyStore.
341    ///
342    /// # Errors
343    /// KeyStore errors, such as IO, and if there is more than one strong reference
344    /// to the connection.
345    pub async fn close(self) -> crate::mls::Result<()> {
346        self.crypto_provider
347            .close()
348            .await
349            .map_err(MlsError::wrap("closing connection with keystore"))
350            .map_err(Into::into)
351    }
352
353    /// see [mls_crypto_provider::MlsCryptoProvider::reseed]
354    pub async fn reseed(&self, seed: Option<EntropySeed>) -> crate::mls::Result<()> {
355        self.crypto_provider
356            .reseed(seed)
357            .map_err(MlsError::wrap("reseeding mls backend"))
358            .map_err(Into::into)
359    }
360
361    /// Generates a brand new client from scratch
362    pub(crate) async fn generate(
363        &self,
364        identifier: ClientIdentifier,
365        backend: &MlsCryptoProvider,
366        ciphersuites: &[MlsCiphersuite],
367        nb_key_package: usize,
368    ) -> Result<()> {
369        self.ensure_unready().await?;
370        let id = identifier.get_id()?;
371        let signature_schemes = ciphersuites
372            .iter()
373            .map(|cs| cs.signature_algorithm())
374            .collect::<HashSet<_>>();
375        self.replace_inner(SessionInner {
376            id: id.into_owned(),
377            identities: Identities::new(signature_schemes.len()),
378            keypackage_lifetime: KEYPACKAGE_DEFAULT_LIFETIME,
379        })
380        .await;
381
382        let identities = identifier.generate_credential_bundles(backend, signature_schemes)?;
383
384        for (sc, id, cb) in identities {
385            self.save_identity(&backend.keystore(), Some(&id), sc, cb).await?;
386        }
387
388        let guard = self.inner.read().await;
389        let SessionInner { identities, .. } = guard.as_ref().ok_or(Error::MlsNotInitialized)?;
390
391        if nb_key_package != 0 {
392            for ciphersuite in ciphersuites.iter().copied() {
393                let ciphersuite_signature_scheme = ciphersuite.signature_algorithm();
394                for credential_bundle in identities.iter().filter_map(|(signature_scheme, credential_bundle)| {
395                    (signature_scheme == ciphersuite_signature_scheme).then_some(credential_bundle)
396                }) {
397                    let credential_type = credential_bundle.credential.credential_type().into();
398                    self.request_key_packages(nb_key_package, ciphersuite, credential_type, backend)
399                        .await?;
400                }
401            }
402        }
403
404        Ok(())
405    }
406
407    /// Loads the client from the keystore.
408    pub(crate) async fn load(
409        &self,
410        backend: &MlsCryptoProvider,
411        id: &ClientId,
412        mut credentials: Vec<(Credential, u64)>,
413        signature_schemes: HashSet<SignatureScheme>,
414    ) -> Result<()> {
415        self.ensure_unready().await?;
416        let mut identities = Identities::new(signature_schemes.len());
417
418        // ensures we load credentials in chronological order
419        credentials.sort_by_key(|(_, timestamp)| *timestamp);
420
421        let stored_signature_keypairs = backend
422            .key_store()
423            .find_all::<MlsSignatureKeyPair>(EntityFindParams::default())
424            .await
425            .map_err(KeystoreError::wrap("finding all mls signature keypairs"))?;
426
427        for signature_scheme in signature_schemes {
428            let signature_keypair = stored_signature_keypairs
429                .iter()
430                .find(|skp| skp.signature_scheme == (signature_scheme as u16));
431
432            let signature_key = if let Some(kp) = signature_keypair {
433                SignatureKeyPair::tls_deserialize(&mut kp.keypair.as_slice())
434                    .map_err(Error::tls_deserialize("signature keypair"))?
435            } else {
436                let (private_key, public_key) = backend
437                    .crypto()
438                    .signature_key_gen(signature_scheme)
439                    .map_err(MlsError::wrap("generating signature key"))?;
440                let keypair = SignatureKeyPair::from_raw(signature_scheme, private_key, public_key.clone());
441                let raw_keypair = keypair
442                    .tls_serialize_detached()
443                    .map_err(Error::tls_serialize("raw keypair"))?;
444                let store_keypair =
445                    MlsSignatureKeyPair::new(signature_scheme, public_key, raw_keypair, id.as_slice().into());
446                backend
447                    .key_store()
448                    .save(store_keypair.clone())
449                    .await
450                    .map_err(KeystoreError::wrap("storing keypairs in keystore"))?;
451                SignatureKeyPair::tls_deserialize(&mut store_keypair.keypair.as_slice())
452                    .map_err(Error::tls_deserialize("signature keypair"))?
453            };
454
455            for (credential, created_at) in &credentials {
456                match credential.mls_credential() {
457                    openmls::prelude::MlsCredentialType::Basic(_) => {
458                        if id.as_slice() != credential.identity() {
459                            return Err(Error::WrongCredential);
460                        }
461                    }
462                    openmls::prelude::MlsCredentialType::X509(cert) => {
463                        let spk = cert
464                            .extract_public_key()
465                            .map_err(RecursiveError::mls_credential("extracting public key"))?
466                            .ok_or(LeafError::InternalMlsError)?;
467                        if signature_key.public() != spk {
468                            return Err(Error::WrongCredential);
469                        }
470                    }
471                };
472                let cb = CredentialBundle {
473                    credential: credential.clone(),
474                    signature_key: signature_key.clone(),
475                    created_at: *created_at,
476                };
477                identities.push_credential_bundle(signature_scheme, cb).await?;
478            }
479        }
480        self.replace_inner(SessionInner {
481            id: id.clone(),
482            identities,
483            keypackage_lifetime: KEYPACKAGE_DEFAULT_LIFETIME,
484        })
485        .await;
486        Ok(())
487    }
488
489    /// Restore from an external [`HistorySecret`].
490    pub(crate) async fn restore_from_history_secret(&self, history_secret: HistorySecret) -> Result<()> {
491        self.ensure_unready().await?;
492
493        // store the client id (with some other stuff)
494        self.replace_inner(SessionInner {
495            id: history_secret.client_id.clone(),
496            identities: Identities::new(0),
497            keypackage_lifetime: KEYPACKAGE_DEFAULT_LIFETIME,
498        })
499        .await;
500
501        // store the key package
502        history_secret
503            .key_package
504            .store(&self.crypto_provider)
505            .await
506            .map_err(MlsError::wrap("storing key package encapsulation"))?;
507
508        Ok(())
509    }
510
511    pub(crate) async fn save_identity(
512        &self,
513        keystore: &Connection,
514        id: Option<&ClientId>,
515        signature_scheme: SignatureScheme,
516        mut credential_bundle: CredentialBundle,
517    ) -> Result<CredentialBundle> {
518        let mut guard = self.inner.write().await;
519        let SessionInner {
520            id: existing_id,
521            identities,
522            ..
523        } = guard.as_mut().ok_or(Error::MlsNotInitialized)?;
524
525        let id = id.unwrap_or(existing_id);
526
527        let credential = credential_bundle
528            .credential
529            .tls_serialize_detached()
530            .map_err(Error::tls_serialize("credential bundle"))?;
531        let credential = MlsCredential {
532            id: id.clone().into(),
533            credential,
534            created_at: 0,
535        };
536
537        let credential = keystore
538            .save(credential)
539            .await
540            .map_err(KeystoreError::wrap("saving credential"))?;
541
542        let sign_kp = MlsSignatureKeyPair::new(
543            signature_scheme,
544            credential_bundle.signature_key.to_public_vec(),
545            credential_bundle
546                .signature_key
547                .tls_serialize_detached()
548                .map_err(Error::tls_serialize("signature keypair"))?,
549            id.clone().into(),
550        );
551        keystore.save(sign_kp).await.map_err(|e| match e {
552            CryptoKeystoreError::AlreadyExists => Error::CredentialBundleConflict,
553            _ => KeystoreError::wrap("saving mls signature key pair")(e).into(),
554        })?;
555
556        // set the creation date of the signature keypair which is the same for the CredentialBundle
557        credential_bundle.created_at = credential.created_at;
558
559        identities
560            .push_credential_bundle(signature_scheme, credential_bundle.clone())
561            .await?;
562
563        Ok(credential_bundle)
564    }
565
566    /// Retrieves the client's client id. This is free-form and not inspected.
567    pub async fn id(&self) -> Result<ClientId> {
568        match self.inner.read().await.deref() {
569            None => Err(Error::MlsNotInitialized),
570            Some(SessionInner { id, .. }) => Ok(id.clone()),
571        }
572    }
573
574    /// Returns whether this client is E2EI capable
575    pub async fn is_e2ei_capable(&self) -> bool {
576        match self.inner.read().await.deref() {
577            None => false,
578            Some(SessionInner { identities, .. }) => identities
579                .iter()
580                .any(|(_, cred)| cred.credential().credential_type() == CredentialType::X509),
581        }
582    }
583
584    pub(crate) async fn get_most_recent_or_create_credential_bundle(
585        &self,
586        backend: &MlsCryptoProvider,
587        sc: SignatureScheme,
588        ct: MlsCredentialType,
589    ) -> Result<Arc<CredentialBundle>> {
590        match ct {
591            MlsCredentialType::Basic => {
592                self.init_basic_credential_bundle_if_missing(backend, sc).await?;
593                self.find_most_recent_credential_bundle(sc, ct).await
594            }
595            MlsCredentialType::X509 => self
596                .find_most_recent_credential_bundle(sc, ct)
597                .await
598                .map_err(|e| match e {
599                    Error::CredentialNotFound(_) => LeafError::E2eiEnrollmentNotDone.into(),
600                    _ => e,
601                }),
602        }
603    }
604
605    pub(crate) async fn init_basic_credential_bundle_if_missing(
606        &self,
607        backend: &MlsCryptoProvider,
608        sc: SignatureScheme,
609    ) -> Result<()> {
610        let existing_cb = self
611            .find_most_recent_credential_bundle(sc, MlsCredentialType::Basic)
612            .await;
613        if matches!(existing_cb, Err(Error::CredentialNotFound(_))) {
614            let id = self.id().await?;
615            debug!(id:% = &id; "Initializing basic credential bundle");
616            let cb = Self::new_basic_credential_bundle(&id, sc, backend)?;
617            self.save_identity(&backend.keystore(), None, sc, cb).await?;
618        }
619        Ok(())
620    }
621
622    pub(crate) async fn save_new_x509_credential_bundle(
623        &self,
624        keystore: &Connection,
625        sc: SignatureScheme,
626        cb: CertificateBundle,
627    ) -> Result<CredentialBundle> {
628        let id = cb
629            .get_client_id()
630            .map_err(RecursiveError::mls_credential("getting client id"))?;
631        let cb = Self::new_x509_credential_bundle(cb)?;
632        self.save_identity(keystore, Some(&id), sc, cb).await
633    }
634}
635
636#[cfg(test)]
637mod tests {
638    use super::*;
639    use crate::test_utils::*;
640    use crate::transaction_context::test_utils::EntitiesCount;
641    use core_crypto_keystore::connection::{ConnectionType, DatabaseKey, FetchFromDatabase};
642    use core_crypto_keystore::entities::*;
643    use mls_crypto_provider::MlsCryptoProvider;
644
645    impl Session {
646        // test functions are not held to the same documentation standard as proper functions
647        #![allow(missing_docs)]
648
649        pub async fn random_generate(
650            &self,
651            case: &crate::test_utils::TestContext,
652            signer: Option<&crate::test_utils::x509::X509Certificate>,
653            provision: bool,
654        ) -> Result<()> {
655            self.reset().await;
656            let user_uuid = uuid::Uuid::new_v4();
657            let rnd_id = rand::random::<usize>();
658            let client_id = format!("{}:{rnd_id:x}@members.wire.com", user_uuid.hyphenated());
659            let identity = match case.credential_type {
660                MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.as_str().into()),
661                MlsCredentialType::X509 => {
662                    let signer = signer.expect("Missing intermediate CA");
663                    CertificateBundle::rand_identifier(&client_id, &[signer])
664                }
665            };
666            let nb_key_package = if provision {
667                crate::prelude::INITIAL_KEYING_MATERIAL_COUNT
668            } else {
669                0
670            };
671            let backend = self.crypto_provider.clone();
672            self.generate(identity, &backend, &[case.ciphersuite()], nb_key_package)
673                .await?;
674            Ok(())
675        }
676
677        pub async fn find_keypackages(&self, backend: &MlsCryptoProvider) -> Result<Vec<openmls::prelude::KeyPackage>> {
678            use core_crypto_keystore::CryptoKeystoreMls as _;
679            let kps = backend
680                .key_store()
681                .mls_fetch_keypackages::<openmls::prelude::KeyPackage>(u32::MAX)
682                .await
683                .map_err(KeystoreError::wrap("fetching mls keypackages"))?;
684            Ok(kps)
685        }
686
687        pub(crate) async fn generate_one_keypackage(
688            &self,
689            backend: &MlsCryptoProvider,
690            cs: MlsCiphersuite,
691            ct: MlsCredentialType,
692        ) -> Result<openmls::prelude::KeyPackage> {
693            let cb = self
694                .find_most_recent_credential_bundle(cs.signature_algorithm(), ct)
695                .await?;
696            self.generate_one_keypackage_from_credential_bundle(backend, cs, &cb)
697                .await
698        }
699
700        /// Count the entities
701        pub async fn count_entities(&self) -> EntitiesCount {
702            let keystore = self.crypto_provider.keystore();
703            let credential = keystore.count::<MlsCredential>().await.unwrap();
704            let encryption_keypair = keystore.count::<MlsEncryptionKeyPair>().await.unwrap();
705            let epoch_encryption_keypair = keystore.count::<MlsEpochEncryptionKeyPair>().await.unwrap();
706            let enrollment = keystore.count::<E2eiEnrollment>().await.unwrap();
707            let group = keystore.count::<PersistedMlsGroup>().await.unwrap();
708            let hpke_private_key = keystore.count::<MlsHpkePrivateKey>().await.unwrap();
709            let key_package = keystore.count::<MlsKeyPackage>().await.unwrap();
710            let pending_group = keystore.count::<PersistedMlsPendingGroup>().await.unwrap();
711            let pending_messages = keystore.count::<MlsPendingMessage>().await.unwrap();
712            let psk_bundle = keystore.count::<MlsPskBundle>().await.unwrap();
713            let signature_keypair = keystore.count::<MlsSignatureKeyPair>().await.unwrap();
714            EntitiesCount {
715                credential,
716                encryption_keypair,
717                epoch_encryption_keypair,
718                enrollment,
719                group,
720                hpke_private_key,
721                key_package,
722                pending_group,
723                pending_messages,
724                psk_bundle,
725                signature_keypair,
726            }
727        }
728    }
729
730    #[apply(all_cred_cipher)]
731    async fn can_generate_session(case: TestContext) {
732        let [alice] = case.sessions().await;
733        let key = DatabaseKey::generate();
734        let key_store = CryptoKeystore::open(ConnectionType::InMemory, &key).await.unwrap();
735        let backend = MlsCryptoProvider::builder().key_store(key_store).build();
736        let x509_test_chain = if case.is_x509() {
737            let x509_test_chain = crate::test_utils::x509::X509TestChain::init_empty(case.signature_scheme());
738            x509_test_chain.register_with_provider(&backend).await;
739            Some(x509_test_chain)
740        } else {
741            None
742        };
743        backend.new_transaction().await.unwrap();
744        let session = alice.session().await;
745        session
746            .random_generate(
747                &case,
748                x509_test_chain.as_ref().map(|chain| chain.find_local_intermediate_ca()),
749                false,
750            )
751            .await
752            .unwrap();
753    }
754}