core_crypto/mls/conversation/
config.rs1use mls_crypto_provider::MlsCryptoProvider;
7use openmls::prelude::{
8 Capabilities, Credential, CredentialType, ExternalSender, OpenMlsSignaturePublicKey,
9 PURE_CIPHERTEXT_WIRE_FORMAT_POLICY, PURE_PLAINTEXT_WIRE_FORMAT_POLICY, ProtocolVersion,
10 RequiredCapabilitiesExtension, SenderRatchetConfiguration, WireFormatPolicy,
11};
12use openmls_traits::{
13 OpenMlsCryptoProvider,
14 crypto::OpenMlsCrypto,
15 types::{Ciphersuite, SignatureScheme},
16};
17use serde::{Deserialize, Serialize};
18use wire_e2e_identity::prelude::parse_json_jwk;
19
20use super::Result;
21use crate::{MlsError, RecursiveError, prelude::MlsCiphersuite};
22
23pub(crate) const MAX_PAST_EPOCHS: usize = 3;
25
26pub(crate) const OUT_OF_ORDER_TOLERANCE: u32 = 2;
29
30pub(crate) const MAXIMUM_FORWARD_DISTANCE: u32 = 1000;
32
33#[derive(Debug, Clone, Default)]
35pub struct MlsConversationConfiguration {
36 pub ciphersuite: MlsCiphersuite,
38 pub external_senders: Vec<ExternalSender>,
40 pub custom: MlsCustomConfiguration,
42}
43
44impl MlsConversationConfiguration {
45 const WIRE_SERVER_IDENTITY: &'static str = "wire-server";
46
47 const PADDING_SIZE: usize = 128;
48
49 pub(crate) const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::Mls10;
51
52 pub(crate) const DEFAULT_SUPPORTED_CREDENTIALS: &'static [CredentialType] =
54 &[CredentialType::Basic, CredentialType::X509];
55
56 pub(crate) const DEFAULT_SUPPORTED_CIPHERSUITES: &'static [Ciphersuite] = &[
58 Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
59 Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
60 Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
61 Ciphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
62 Ciphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521,
63 ];
64
65 const NUMBER_RESUMPTION_PSK: usize = 1;
67
68 #[inline(always)]
70 pub fn as_openmls_default_configuration(&self) -> Result<openmls::group::MlsGroupConfig> {
71 let crypto_config = openmls::prelude::CryptoConfig {
72 version: Self::DEFAULT_PROTOCOL_VERSION,
73 ciphersuite: self.ciphersuite.into(),
74 };
75 Ok(openmls::group::MlsGroupConfig::builder()
76 .wire_format_policy(self.custom.wire_policy.into())
77 .max_past_epochs(MAX_PAST_EPOCHS)
78 .padding_size(Self::PADDING_SIZE)
79 .number_of_resumption_psks(Self::NUMBER_RESUMPTION_PSK)
80 .leaf_capabilities(Self::default_leaf_capabilities())
81 .required_capabilities(self.default_required_capabilities())
82 .sender_ratchet_configuration(SenderRatchetConfiguration::new(
83 self.custom.out_of_order_tolerance,
84 self.custom.maximum_forward_distance,
85 ))
86 .use_ratchet_tree_extension(true)
87 .external_senders(self.external_senders.clone())
88 .crypto_config(crypto_config)
89 .build())
90 }
91
92 pub fn default_leaf_capabilities() -> Capabilities {
94 Capabilities::new(
95 Some(&[Self::DEFAULT_PROTOCOL_VERSION]),
96 Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES),
97 Some(&[]),
98 Some(&[]),
99 Some(Self::DEFAULT_SUPPORTED_CREDENTIALS),
100 )
101 }
102
103 fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension {
104 RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS)
105 }
106
107 pub(crate) fn parse_external_sender(jwk: &[u8]) -> Result<ExternalSender> {
109 let pk = parse_json_jwk(jwk)
110 .map_err(wire_e2e_identity::prelude::E2eIdentityError::from)
111 .map_err(crate::e2e_identity::Error::from)
112 .map_err(RecursiveError::e2e_identity("parsing jwk"))?;
113 Ok(ExternalSender::new(
114 pk.into(),
115 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
116 ))
117 }
118
119 pub(crate) fn legacy_external_sender(
123 key: Vec<u8>,
124 signature_scheme: SignatureScheme,
125 backend: &MlsCryptoProvider,
126 ) -> Result<ExternalSender> {
127 backend
128 .crypto()
129 .validate_signature_key(signature_scheme, &key[..])
130 .map_err(MlsError::wrap("validating signature key"))?;
131 let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme)
132 .map_err(MlsError::wrap("creating new signature public key"))?;
133 Ok(ExternalSender::new(
134 key.into(),
135 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
136 ))
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct MlsCustomConfiguration {
143 pub key_rotation_span: Option<std::time::Duration>,
146 pub wire_policy: MlsWirePolicy,
148 pub out_of_order_tolerance: u32,
152 pub maximum_forward_distance: u32,
155}
156
157impl Default for MlsCustomConfiguration {
158 fn default() -> Self {
159 Self {
160 wire_policy: MlsWirePolicy::Plaintext,
161 key_rotation_span: Default::default(),
162 out_of_order_tolerance: OUT_OF_ORDER_TOLERANCE,
163 maximum_forward_distance: MAXIMUM_FORWARD_DISTANCE,
164 }
165 }
166}
167
168#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
170#[repr(u8)]
171pub enum MlsWirePolicy {
172 #[default]
174 Plaintext = 1,
175 Ciphertext = 2,
177}
178
179impl From<MlsWirePolicy> for WireFormatPolicy {
180 fn from(policy: MlsWirePolicy) -> Self {
181 match policy {
182 MlsWirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
183 MlsWirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use openmls::prelude::ProtocolVersion;
191 use openmls_traits::{
192 OpenMlsCryptoProvider,
193 crypto::OpenMlsCrypto,
194 types::{SignatureScheme, VerifiableCiphersuite},
195 };
196 use wasm_bindgen_test::*;
197 use wire_e2e_identity::prelude::JwsAlgorithm;
198
199 use crate::mls::conversation::ConversationWithMls as _;
200 use crate::{prelude::MlsConversationConfiguration, test_utils::*};
201
202 wasm_bindgen_test_configure!(run_in_browser);
203
204 #[cfg_attr(not(target_family = "wasm"), async_std::test)]
205 #[wasm_bindgen_test]
206 pub async fn group_should_have_required_capabilities() {
207 let case = TestCase::default();
208 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
209 Box::pin(async move {
210 let id = conversation_id();
211 cc.context
212 .new_conversation(&id, case.credential_type, case.cfg.clone())
213 .await
214 .unwrap();
215 let conv = cc.context.conversation(&id).await.unwrap();
216 let group = conv.conversation().await;
217
218 let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
219
220 assert!(capabilities.extension_types().is_empty());
222 assert!(capabilities.proposal_types().is_empty());
223 assert_eq!(
224 capabilities.credential_types(),
225 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
226 );
227 })
228 })
229 .await
230 }
231
232 #[apply(all_cred_cipher)]
233 #[wasm_bindgen_test]
234 pub async fn creator_leaf_node_should_have_default_capabilities(case: TestCase) {
235 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
236 Box::pin(async move {
237 let id = conversation_id();
238 cc.context
239 .new_conversation(&id, case.credential_type, case.cfg.clone())
240 .await
241 .unwrap();
242 let conv = cc.context.conversation(&id).await.unwrap();
243 let group = conv.conversation().await;
244
245 let creator_capabilities = group.group.own_leaf().unwrap().capabilities();
247
248 assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
251
252 assert_eq!(
254 creator_capabilities.ciphersuites().to_vec(),
255 MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
256 .iter()
257 .map(|c| VerifiableCiphersuite::from(*c))
258 .collect::<Vec<_>>()
259 );
260
261 assert!(creator_capabilities.proposals().is_empty());
263
264 assert!(creator_capabilities.extensions().is_empty(),);
266
267 assert_eq!(
269 creator_capabilities.credentials(),
270 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
271 );
272 })
273 })
274 .await
275 }
276
277 #[apply(all_cred_cipher)]
278 #[wasm_bindgen_test]
279 pub async fn should_support_raw_external_sender(case: TestCase) {
280 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
281 Box::pin(async move {
282 let (_sk, pk) = cc
283 .context
284 .mls_provider()
285 .await
286 .unwrap()
287 .crypto()
288 .signature_key_gen(case.signature_scheme())
289 .unwrap();
290
291 assert!(
292 cc.context
293 .set_raw_external_senders(&mut case.cfg.clone(), vec![pk])
294 .await
295 .is_ok()
296 );
297 })
298 })
299 .await
300 }
301
302 #[apply(all_cred_cipher)]
303 #[wasm_bindgen_test]
304 pub async fn should_support_jwk_external_sender(case: TestCase) {
305 run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
306 Box::pin(async move {
307 let sc = case.signature_scheme();
308
309 let alg = match sc {
310 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
311 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
312 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
313 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
314 SignatureScheme::ED448 => unreachable!(),
315 };
316
317 let jwk = wire_e2e_identity::prelude::generate_jwk(alg);
318 cc.context
319 .set_raw_external_senders(&mut case.cfg.clone(), vec![jwk])
320 .await
321 .unwrap();
322 })
323 })
324 .await;
325 }
326}