core_crypto/mls/conversation/
config.rs1use mls_crypto_provider::MlsCryptoProvider;
23use openmls::prelude::{
24 Capabilities, Credential, CredentialType, ExternalSender, OpenMlsSignaturePublicKey, ProtocolVersion,
25 RequiredCapabilitiesExtension, SenderRatchetConfiguration, WireFormatPolicy, PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
26 PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
27};
28use openmls_traits::crypto::OpenMlsCrypto;
29use openmls_traits::types::{Ciphersuite, SignatureScheme};
30use openmls_traits::OpenMlsCryptoProvider;
31use serde::{Deserialize, Serialize};
32use wire_e2e_identity::prelude::parse_json_jwk;
33
34use crate::context::CentralContext;
35use crate::prelude::{CryptoResult, E2eIdentityError, MlsCiphersuite};
36use crate::MlsError;
37
38pub(crate) const MAX_PAST_EPOCHS: usize = 3;
40
41pub(crate) const OUT_OF_ORDER_TOLERANCE: u32 = 2;
44
45pub(crate) const MAXIMUM_FORWARD_DISTANCE: u32 = 1000;
47
48impl CentralContext {
49 pub async fn set_raw_external_senders(
51 &self,
52 cfg: &mut MlsConversationConfiguration,
53 external_senders: Vec<Vec<u8>>,
54 ) -> CryptoResult<()> {
55 let mls_provider = self.mls_provider().await?;
56 cfg.external_senders = external_senders
57 .into_iter()
58 .map(|key| {
59 MlsConversationConfiguration::parse_external_sender(&key).or_else(|_| {
60 MlsConversationConfiguration::legacy_external_sender(
61 key,
62 cfg.ciphersuite.signature_algorithm(),
63 &mls_provider,
64 )
65 })
66 })
67 .collect::<CryptoResult<_>>()?;
68 Ok(())
69 }
70}
71
72#[derive(Debug, Clone, Default)]
74pub struct MlsConversationConfiguration {
75 pub ciphersuite: MlsCiphersuite,
77 pub external_senders: Vec<ExternalSender>,
79 pub custom: MlsCustomConfiguration,
81}
82
83impl MlsConversationConfiguration {
84 const WIRE_SERVER_IDENTITY: &'static str = "wire-server";
85
86 const PADDING_SIZE: usize = 128;
87
88 pub(crate) const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::Mls10;
90
91 pub(crate) const DEFAULT_SUPPORTED_CREDENTIALS: &'static [CredentialType] =
93 &[CredentialType::Basic, CredentialType::X509];
94
95 pub(crate) const DEFAULT_SUPPORTED_CIPHERSUITES: &'static [Ciphersuite] = &[
97 Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
98 Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
99 Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
100 Ciphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
101 Ciphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521,
102 ];
103
104 const NUMBER_RESUMPTION_PSK: usize = 1;
106
107 #[inline(always)]
109 pub fn as_openmls_default_configuration(&self) -> CryptoResult<openmls::group::MlsGroupConfig> {
110 let crypto_config = openmls::prelude::CryptoConfig {
111 version: Self::DEFAULT_PROTOCOL_VERSION,
112 ciphersuite: self.ciphersuite.into(),
113 };
114 Ok(openmls::group::MlsGroupConfig::builder()
115 .wire_format_policy(self.custom.wire_policy.into())
116 .max_past_epochs(MAX_PAST_EPOCHS)
117 .padding_size(Self::PADDING_SIZE)
118 .number_of_resumption_psks(Self::NUMBER_RESUMPTION_PSK)
119 .leaf_capabilities(Self::default_leaf_capabilities())
120 .required_capabilities(self.default_required_capabilities())
121 .sender_ratchet_configuration(SenderRatchetConfiguration::new(
122 self.custom.out_of_order_tolerance,
123 self.custom.maximum_forward_distance,
124 ))
125 .use_ratchet_tree_extension(true)
126 .external_senders(self.external_senders.clone())
127 .crypto_config(crypto_config)
128 .build())
129 }
130
131 pub fn default_leaf_capabilities() -> Capabilities {
133 Capabilities::new(
134 Some(&[Self::DEFAULT_PROTOCOL_VERSION]),
135 Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES),
136 Some(&[]),
137 Some(&[]),
138 Some(Self::DEFAULT_SUPPORTED_CREDENTIALS),
139 )
140 }
141
142 fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension {
143 RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS)
144 }
145
146 fn parse_external_sender(jwk: &[u8]) -> CryptoResult<ExternalSender> {
148 let pk = parse_json_jwk(jwk)
149 .map_err(wire_e2e_identity::prelude::E2eIdentityError::from)
150 .map_err(E2eIdentityError::from)?;
151 Ok(ExternalSender::new(
152 pk.into(),
153 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
154 ))
155 }
156
157 fn legacy_external_sender(
161 key: Vec<u8>,
162 signature_scheme: SignatureScheme,
163 backend: &MlsCryptoProvider,
164 ) -> CryptoResult<ExternalSender> {
165 backend
166 .crypto()
167 .validate_signature_key(signature_scheme, &key[..])
168 .map_err(MlsError::from)?;
169 let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme).map_err(MlsError::from)?;
170 Ok(ExternalSender::new(
171 key.into(),
172 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
173 ))
174 }
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct MlsCustomConfiguration {
180 pub key_rotation_span: Option<std::time::Duration>,
183 pub wire_policy: MlsWirePolicy,
185 pub out_of_order_tolerance: u32,
189 pub maximum_forward_distance: u32,
192}
193
194impl Default for MlsCustomConfiguration {
195 fn default() -> Self {
196 Self {
197 wire_policy: MlsWirePolicy::Plaintext,
198 key_rotation_span: Default::default(),
199 out_of_order_tolerance: OUT_OF_ORDER_TOLERANCE,
200 maximum_forward_distance: MAXIMUM_FORWARD_DISTANCE,
201 }
202 }
203}
204
205#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
207#[repr(u8)]
208pub enum MlsWirePolicy {
209 #[default]
211 Plaintext = 1,
212 Ciphertext = 2,
214}
215
216impl From<MlsWirePolicy> for WireFormatPolicy {
217 fn from(policy: MlsWirePolicy) -> Self {
218 match policy {
219 MlsWirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
220 MlsWirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use openmls::prelude::ProtocolVersion;
228 use openmls_traits::{
229 crypto::OpenMlsCrypto,
230 types::{SignatureScheme, VerifiableCiphersuite},
231 OpenMlsCryptoProvider,
232 };
233 use wasm_bindgen_test::*;
234 use wire_e2e_identity::prelude::JwsAlgorithm;
235
236 use crate::{prelude::MlsConversationConfiguration, test_utils::*};
237
238 wasm_bindgen_test_configure!(run_in_browser);
239
240 #[cfg_attr(not(target_family = "wasm"), async_std::test)]
241 #[wasm_bindgen_test]
242 pub async fn group_should_have_required_capabilities() {
243 let case = TestCase::default();
244 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
245 Box::pin(async move {
246 let id = conversation_id();
247 cc.context
248 .new_conversation(&id, case.credential_type, case.cfg.clone())
249 .await
250 .unwrap();
251 let conv = cc.context.get_conversation(&id).await.unwrap();
252 let group = conv.read().await;
253
254 let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
255
256 assert!(capabilities.extension_types().is_empty());
258 assert!(capabilities.proposal_types().is_empty());
259 assert_eq!(
260 capabilities.credential_types(),
261 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
262 );
263 })
264 })
265 .await
266 }
267
268 #[apply(all_cred_cipher)]
269 #[wasm_bindgen_test]
270 pub async fn creator_leaf_node_should_have_default_capabilities(case: TestCase) {
271 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
272 Box::pin(async move {
273 let id = conversation_id();
274 cc.context
275 .new_conversation(&id, case.credential_type, case.cfg.clone())
276 .await
277 .unwrap();
278 let conv = cc.context.get_conversation(&id).await.unwrap();
279 let group = conv.read().await;
280
281 let creator_capabilities = group.group.own_leaf().unwrap().capabilities();
283
284 assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
287
288 assert_eq!(
290 creator_capabilities.ciphersuites().to_vec(),
291 MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
292 .iter()
293 .map(|c| VerifiableCiphersuite::from(*c))
294 .collect::<Vec<_>>()
295 );
296
297 assert!(creator_capabilities.proposals().is_empty());
299
300 assert!(creator_capabilities.extensions().is_empty(),);
302
303 assert_eq!(
305 creator_capabilities.credentials(),
306 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
307 );
308 })
309 })
310 .await
311 }
312
313 #[apply(all_cred_cipher)]
314 #[wasm_bindgen_test]
315 pub async fn should_support_raw_external_sender(case: TestCase) {
316 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
317 Box::pin(async move {
318 let (_sk, pk) = cc
319 .context
320 .mls_provider()
321 .await
322 .unwrap()
323 .crypto()
324 .signature_key_gen(case.signature_scheme())
325 .unwrap();
326
327 assert!(cc
328 .context
329 .set_raw_external_senders(&mut case.cfg.clone(), vec![pk])
330 .await
331 .is_ok());
332 })
333 })
334 .await
335 }
336
337 #[apply(all_cred_cipher)]
338 #[wasm_bindgen_test]
339 pub async fn should_support_jwk_external_sender(case: TestCase) {
340 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
341 Box::pin(async move {
342 let sc = case.signature_scheme();
343
344 let alg = match sc {
345 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
346 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
347 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
348 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
349 SignatureScheme::ED448 => unreachable!(),
350 };
351
352 let jwk = wire_e2e_identity::prelude::generate_jwk(alg);
353 cc.context
354 .set_raw_external_senders(&mut case.cfg.clone(), vec![jwk])
355 .await
356 .unwrap();
357 })
358 })
359 .await;
360 }
361}