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