core_crypto/mls/
mod.rs

1use mls_crypto_provider::MlsCryptoProvider;
2
3use crate::{ClientId, MlsConversation, Session};
4
5pub(crate) mod ciphersuite;
6pub mod conversation;
7pub(crate) mod credential;
8mod error;
9pub(crate) mod proposal;
10pub(crate) mod session;
11
12pub use error::{Error, Result};
13pub use session::{EpochObserver, HistoryObserver};
14
15#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
16#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
17pub(crate) trait HasSessionAndCrypto: Send {
18    async fn session(&self) -> Result<Session>;
19    async fn crypto_provider(&self) -> Result<MlsCryptoProvider>;
20}
21
22#[cfg(test)]
23mod tests {
24
25    use crate::{
26        CertificateBundle, ClientIdentifier, CoreCrypto, MlsCredentialType, SessionConfig,
27        mls::Session,
28        test_utils::{x509::X509TestChain, *},
29        transaction_context::Error as TransactionError,
30    };
31
32    mod conversation_epoch {
33        use super::*;
34        use crate::mls::conversation::Conversation as _;
35
36        #[apply(all_cred_cipher)]
37        async fn can_get_newly_created_conversation_epoch(case: TestContext) {
38            let [session] = case.sessions().await;
39            let conversation = case.create_conversation([&session]).await;
40            let epoch = conversation.guard().await.epoch().await;
41            assert_eq!(epoch, 0);
42        }
43
44        #[apply(all_cred_cipher)]
45        async fn can_get_conversation_epoch(case: TestContext) {
46            let [alice, bob] = case.sessions().await;
47            Box::pin(async move {
48                let conversation = case.create_conversation([&alice, &bob]).await;
49                let epoch = conversation.guard().await.epoch().await;
50                assert_eq!(epoch, 1);
51            })
52            .await;
53        }
54
55        #[apply(all_cred_cipher)]
56        async fn conversation_not_found(case: TestContext) {
57            use crate::LeafError;
58            let [session] = case.sessions().await;
59            let id = conversation_id();
60            let err = session.transaction.conversation(&id).await.unwrap_err();
61            assert!(matches!(
62                err,
63                TransactionError::Leaf(LeafError::ConversationNotFound(i)) if i == id
64            ));
65        }
66    }
67
68    mod invariants {
69        use super::*;
70        use crate::{MlsCiphersuite, mls};
71
72        #[apply(all_cred_cipher)]
73        async fn can_create_from_valid_configuration(mut case: TestContext) {
74            let db = case.create_persistent_db().await;
75            Box::pin(async move {
76                let configuration = SessionConfig::builder()
77                    .database(db)
78                    .client_id("alice".into())
79                    .ciphersuites([case.ciphersuite()])
80                    .build()
81                    .validate()
82                    .unwrap();
83
84                let new_client_result = Session::try_new(configuration).await;
85                assert!(new_client_result.is_ok())
86            })
87            .await
88        }
89
90        #[macro_rules_attribute::apply(smol_macros::test)]
91        async fn client_id_should_not_be_empty() {
92            let mut case = TestContext::default();
93            let db = case.create_persistent_db().await;
94            Box::pin(async move {
95                let config_err = SessionConfig::builder()
96                    .database(db)
97                    .client_id("".into())
98                    .ciphersuites([MlsCiphersuite::default()])
99                    .build()
100                    .validate()
101                    .unwrap_err();
102
103                assert!(matches!(config_err, mls::Error::MalformedIdentifier("client_id")));
104            })
105            .await
106        }
107    }
108
109    #[apply(all_cred_cipher)]
110    async fn create_conversation_should_fail_when_already_exists(case: TestContext) {
111        use crate::LeafError;
112
113        let [alice] = case.sessions().await;
114        Box::pin(async move {
115            let conversation = case.create_conversation([&alice]).await;
116            let id = conversation.id().clone();
117
118                // creating a conversation should first verify that the conversation does not already exist ; only then create it
119                let repeat_create = alice
120                    .transaction
121                    .new_conversation(&id, case.credential_type, case.cfg.clone())
122                    .await;
123                assert!(matches!(repeat_create.unwrap_err(), TransactionError::Leaf(LeafError::ConversationAlreadyExists(i)) if i == id));
124            })
125        .await;
126    }
127
128    #[apply(all_cred_cipher)]
129    async fn can_fetch_client_public_key(mut case: TestContext) {
130        let db = case.create_persistent_db().await;
131        Box::pin(async move {
132            let configuration = SessionConfig::builder()
133                .database(db)
134                .client_id("potato".into())
135                .ciphersuites([case.ciphersuite()])
136                .build()
137                .validate()
138                .unwrap();
139
140            let result = Session::try_new(configuration).await;
141            println!("{result:?}");
142            assert!(result.is_ok());
143        })
144        .await
145    }
146
147    #[apply(all_cred_cipher)]
148    async fn can_2_phase_init_central(mut case: TestContext) {
149        let db = case.create_persistent_db().await;
150        Box::pin(async move {
151            let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
152            let configuration = SessionConfig::builder()
153                .database(db)
154                .ciphersuites([case.ciphersuite()])
155                .build()
156                .validate()
157                .unwrap();
158
159            // phase 1: init without initialized mls_client
160            let client = Session::try_new(configuration).await.unwrap();
161            let cc = CoreCrypto::from(client);
162            let context = cc.new_transaction().await.unwrap();
163            x509_test_chain.register_with_central(&context).await;
164
165            assert!(!context.session().await.unwrap().is_ready().await);
166            // phase 2: init mls_client
167            let client_id = "alice";
168            let identifier = match case.credential_type {
169                MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.into()),
170                MlsCredentialType::X509 => {
171                    CertificateBundle::rand_identifier(client_id, &[x509_test_chain.find_local_intermediate_ca()])
172                }
173            };
174            context.mls_init(identifier, &[case.ciphersuite()]).await.unwrap();
175            assert!(context.session().await.unwrap().is_ready().await);
176            // expect mls_client to work
177            assert_eq!(
178                context
179                    .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 2)
180                    .await
181                    .unwrap()
182                    .len(),
183                2
184            );
185        })
186        .await
187    }
188}