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