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 = TestContext::default();
208
209 let [cc] = case.sessions().await;
210 Box::pin(async move {
211 let id = conversation_id();
212 cc.transaction
213 .new_conversation(&id, case.credential_type, case.cfg.clone())
214 .await
215 .unwrap();
216 let conv = cc.transaction.conversation(&id).await.unwrap();
217 let group = conv.conversation().await;
218
219 let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
220
221 assert!(capabilities.extension_types().is_empty());
223 assert!(capabilities.proposal_types().is_empty());
224 assert_eq!(
225 capabilities.credential_types(),
226 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
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: TestContext) {
235 let [cc] = case.sessions().await;
236 Box::pin(async move {
237 let id = conversation_id();
238 cc.transaction
239 .new_conversation(&id, case.credential_type, case.cfg.clone())
240 .await
241 .unwrap();
242 let conv = cc.transaction.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 .await
274 }
275
276 #[apply(all_cred_cipher)]
277 #[wasm_bindgen_test]
278 pub async fn should_support_raw_external_sender(case: TestContext) {
279 let [cc] = case.sessions().await;
280 Box::pin(async move {
281 let (_sk, pk) = cc
282 .transaction
283 .mls_provider()
284 .await
285 .unwrap()
286 .crypto()
287 .signature_key_gen(case.signature_scheme())
288 .unwrap();
289
290 assert!(
291 cc.transaction
292 .set_raw_external_senders(&mut case.cfg.clone(), vec![pk])
293 .await
294 .is_ok()
295 );
296 })
297 .await
298 }
299
300 #[apply(all_cred_cipher)]
301 #[wasm_bindgen_test]
302 pub async fn should_support_jwk_external_sender(case: TestContext) {
303 let [cc] = case.sessions().await;
304 Box::pin(async move {
305 let sc = case.signature_scheme();
306
307 let alg = match sc {
308 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
309 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
310 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
311 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
312 SignatureScheme::ED448 => unreachable!(),
313 };
314
315 let jwk = wire_e2e_identity::prelude::generate_jwk(alg);
316 cc.transaction
317 .set_raw_external_senders(&mut case.cfg.clone(), vec![jwk])
318 .await
319 .unwrap();
320 })
321 .await;
322 }
323}