core_crypto/mls/conversation/
config.rs1use openmls::prelude::{
6 Capabilities, CredentialType, PURE_CIPHERTEXT_WIRE_FORMAT_POLICY, PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
7 ProtocolVersion, RequiredCapabilitiesExtension, SenderRatchetConfiguration, WireFormatPolicy,
8};
9use openmls_traits::types::Ciphersuite as MlsCiphersuite;
10use serde::{Deserialize, Serialize};
11
12use super::Result;
13use crate::{CipherSuite, ExternalSender};
14
15pub(crate) const MAX_PAST_EPOCHS: usize = 3;
17
18pub(crate) const MAX_FUTURE_EPOCHS: u64 = 1;
20
21pub(crate) const OUT_OF_ORDER_TOLERANCE: u32 = 2;
24
25pub(crate) const MAXIMUM_FORWARD_DISTANCE: u32 = 1000;
27
28#[derive(Debug, Clone, Default)]
30pub struct ConversationConfiguration {
31 pub cipher_suite: CipherSuite,
33 pub external_senders: Vec<ExternalSender>,
35}
36
37impl ConversationConfiguration {
38 pub(crate) const PADDING_SIZE: usize = 128;
40
41 pub(crate) const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::Mls10;
43
44 pub(crate) const DEFAULT_SUPPORTED_CREDENTIALS: &'static [CredentialType] =
46 &[CredentialType::Basic, CredentialType::X509];
47
48 pub(crate) const DEFAULT_SUPPORTED_CIPHERSUITES: &'static [MlsCiphersuite] = &[
50 MlsCiphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
51 MlsCiphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
52 MlsCiphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
53 MlsCiphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
54 MlsCiphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521,
55 MlsCiphersuite::MLS_128_MLKEM768X25519_AES128GCM_SHA256_Ed25519,
57 MlsCiphersuite::MLS_128_MLKEM768X25519_AES256GCM_SHA384_Ed25519,
58 MlsCiphersuite::MLS_128_MLKEM768P256_AES128GCM_SHA256_P256,
59 MlsCiphersuite::MLS_128_MLKEM768P256_AES256GCM_SHA384_P256,
60 MlsCiphersuite::MLS_192_MLKEM1024P384_AES256GCM_SHA384_P384,
61 MlsCiphersuite::MLS_128_MLKEM768_AES256GCM_SHA384_P256,
62 MlsCiphersuite::MLS_192_MLKEM1024_AES256GCM_SHA384_P384,
63 MlsCiphersuite::MLS_192_MLKEM768_AES256GCM_SHA384_MLDSA65,
64 MlsCiphersuite::MLS_256_MLKEM1024_AES256GCM_SHA384_MLDSA87,
65 MlsCiphersuite::MLS_128_MLKEM768_AES256GCM_SHA384_Ed25519,
66 MlsCiphersuite::MLS_128_MLKEM768X25519_CHACHA20POLY1305_SHA384_MLDSA44,
67 ];
68
69 const NUMBER_RESUMPTION_PSK: usize = 1;
71
72 #[inline(always)]
74 pub fn as_openmls_default_configuration(&self) -> Result<openmls::group::MlsGroupConfig> {
75 let crypto_config = openmls::prelude::CryptoConfig {
76 version: Self::DEFAULT_PROTOCOL_VERSION,
77 ciphersuite: self.cipher_suite.into(),
78 };
79 Ok(openmls::group::MlsGroupConfig::builder()
80 .wire_format_policy(WirePolicy::Plaintext.into())
81 .max_past_epochs(MAX_PAST_EPOCHS)
82 .padding_size(Self::PADDING_SIZE)
83 .number_of_resumption_psks(Self::NUMBER_RESUMPTION_PSK)
84 .leaf_capabilities(Self::default_leaf_capabilities())
85 .required_capabilities(self.default_required_capabilities())
86 .sender_ratchet_configuration(SenderRatchetConfiguration::new(
87 OUT_OF_ORDER_TOLERANCE,
88 MAXIMUM_FORWARD_DISTANCE,
89 ))
90 .use_ratchet_tree_extension(true)
91 .external_senders(self.external_senders.iter().cloned().map(Into::into).collect())
92 .crypto_config(crypto_config)
93 .build())
94 }
95
96 pub fn default_leaf_capabilities() -> Capabilities {
98 Capabilities::new(
99 Some(&[Self::DEFAULT_PROTOCOL_VERSION]),
100 Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES),
101 Some(&[]),
102 Some(&[]),
103 Some(Self::DEFAULT_SUPPORTED_CREDENTIALS),
104 )
105 }
106
107 fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension {
108 RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS)
109 }
110
111 pub fn set_external_senders(&mut self, external_senders: impl IntoIterator<Item = ExternalSender>) -> Result<()> {
114 self.external_senders = external_senders.into_iter().collect();
115 Ok(())
116 }
117}
118
119#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
121#[repr(u8)]
122pub enum WirePolicy {
123 #[default]
125 Plaintext = 1,
126 Ciphertext = 2,
128}
129
130impl From<WirePolicy> for WireFormatPolicy {
131 fn from(policy: WirePolicy) -> Self {
132 match policy {
133 WirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
134 WirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use openmls::prelude::ProtocolVersion;
142 use openmls_traits::{
143 OpenMlsCryptoProvider,
144 crypto::OpenMlsCrypto,
145 types::{SignatureScheme, VerifiableCiphersuite},
146 };
147 use wire_e2e_identity::JwsAlgorithm;
148
149 use crate::{ConversationConfiguration, ExternalSender, test_utils::*};
150
151 #[macro_rules_attribute::apply(smol_macros::test)]
152 async fn group_should_have_required_capabilities() {
153 let case = TestContext::default();
154
155 let [session] = case.sessions().await;
156 Box::pin(async move {
157 let conversation = case.create_conversation([&session]).await;
158 let guard = conversation.guard().await;
159 let group = guard.group().await;
160
161 let capabilities = group.group_context_extensions().required_capabilities().unwrap();
162
163 assert!(capabilities.extension_types().is_empty());
165 assert!(capabilities.proposal_types().is_empty());
166 assert_eq!(
167 capabilities.credential_types(),
168 ConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
169 );
170 })
171 .await
172 }
173
174 #[apply(all_cred_cipher)]
175 pub async fn creator_leaf_node_should_have_default_capabilities(case: TestContext) {
176 let [session] = case.sessions().await;
177 Box::pin(async move {
178 let conversation = case.create_conversation([&session]).await;
179 let guard = conversation.guard().await;
180 let group = guard.group().await;
181
182 let creator_capabilities = group.own_leaf().unwrap().capabilities();
184
185 assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
188
189 assert_eq!(
191 creator_capabilities.ciphersuites().to_vec(),
192 ConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
193 .iter()
194 .map(|c| VerifiableCiphersuite::from(*c))
195 .collect::<Vec<_>>()
196 );
197
198 assert!(creator_capabilities.proposals().is_empty());
200
201 assert!(creator_capabilities.extensions().is_empty(),);
203
204 assert_eq!(
206 creator_capabilities.credentials(),
207 ConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
208 );
209 })
210 .await
211 }
212
213 #[apply(all_cred_cipher)]
214 pub async fn should_support_raw_external_sender(case: TestContext) {
215 let [cc] = case.sessions().await;
216 Box::pin(async move {
217 let (_sk, pk) = cc
218 .transaction
219 .crypto_provider()
220 .await
221 .unwrap()
222 .crypto()
223 .signature_key_gen(case.signature_scheme())
224 .unwrap();
225 let pk = ExternalSender::parse_public_key(&pk, case.signature_scheme()).unwrap();
226
227 assert!(case.cfg.clone().set_external_senders([pk]).is_ok());
228 })
229 .await
230 }
231
232 #[apply(all_cred_cipher)]
233 pub async fn should_support_jwk_external_sender(case: TestContext) {
234 Box::pin(async move {
235 let sc = case.signature_scheme();
236
237 let alg = match sc {
238 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
239 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
240 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
241 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
242 SignatureScheme::ED448
244 | SignatureScheme::MLDSA44
245 | SignatureScheme::MLDSA65
246 | SignatureScheme::MLDSA87 => return,
247 };
248
249 let jwk = rusty_jwt_tools::prelude::generate_jwk(alg);
250 let external_sender = ExternalSender::parse_jwk(&jwk).unwrap();
251 assert!(case.cfg.clone().set_external_senders([external_sender]).is_ok());
252 })
253 .await;
254 }
255}