core_crypto/mls/conversation/
config.rs1use openmls::prelude::{
7 Capabilities, Credential, CredentialType, ExternalSender, OpenMlsSignaturePublicKey,
8 PURE_CIPHERTEXT_WIRE_FORMAT_POLICY, PURE_PLAINTEXT_WIRE_FORMAT_POLICY, ProtocolVersion,
9 RequiredCapabilitiesExtension, SenderRatchetConfiguration, WireFormatPolicy,
10};
11use openmls_traits::{
12 crypto::OpenMlsCrypto,
13 types::{Ciphersuite as MlsCiphersuite, SignatureScheme},
14};
15use serde::{Deserialize, Serialize};
16use wire_e2e_identity::parse_json_jwk;
17
18use super::Result;
19use crate::{
20 CipherSuite, MlsError, RecursiveError, mls::conversation::ExternalSenderKey, mls_provider::MlsCryptoProvider,
21};
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: CipherSuite,
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 [MlsCiphersuite] = &[
58 MlsCiphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
59 MlsCiphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
60 MlsCiphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
61 MlsCiphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
62 MlsCiphersuite::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 async fn set_raw_external_senders(
110 &mut self,
111 mls_crypto_provider: &MlsCryptoProvider,
112 external_senders: impl IntoIterator<Item = ExternalSenderKey>,
113 ) -> Result<()> {
114 self.external_senders = external_senders
115 .into_iter()
116 .map(|key| {
117 MlsConversationConfiguration::parse_external_sender(&key).or_else(|_| {
118 MlsConversationConfiguration::legacy_external_sender(
119 key.into(),
120 self.ciphersuite.signature_algorithm(),
121 mls_crypto_provider,
122 )
123 })
124 })
125 .collect::<crate::mls::conversation::Result<_>>()
126 .map_err(RecursiveError::mls_conversation("setting external sender"))?;
127 Ok(())
128 }
129
130 pub(crate) fn parse_external_sender(jwk: &[u8]) -> Result<ExternalSender> {
132 let pk = parse_json_jwk(jwk)
133 .map_err(wire_e2e_identity::E2eIdentityError::from)
134 .map_err(RecursiveError::e2e_identity("parsing jwk"))?;
135 Ok(ExternalSender::new(
136 pk.into(),
137 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
138 ))
139 }
140
141 pub(crate) fn legacy_external_sender(
145 key: Vec<u8>,
146 signature_scheme: SignatureScheme,
147 backend: &MlsCryptoProvider,
148 ) -> Result<ExternalSender> {
149 backend
150 .validate_signature_key(signature_scheme, &key[..])
151 .map_err(MlsError::wrap("validating signature key"))?;
152 let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme)
153 .map_err(MlsError::wrap("creating new signature public key"))?;
154 Ok(ExternalSender::new(
155 key.into(),
156 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
157 ))
158 }
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct MlsCustomConfiguration {
164 pub key_rotation_span: Option<std::time::Duration>,
167 pub wire_policy: MlsWirePolicy,
169 pub out_of_order_tolerance: u32,
173 pub maximum_forward_distance: u32,
176}
177
178impl Default for MlsCustomConfiguration {
179 fn default() -> Self {
180 Self {
181 wire_policy: MlsWirePolicy::Plaintext,
182 key_rotation_span: Default::default(),
183 out_of_order_tolerance: OUT_OF_ORDER_TOLERANCE,
184 maximum_forward_distance: MAXIMUM_FORWARD_DISTANCE,
185 }
186 }
187}
188
189#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
191#[repr(u8)]
192pub enum MlsWirePolicy {
193 #[default]
195 Plaintext = 1,
196 Ciphertext = 2,
198}
199
200impl From<MlsWirePolicy> for WireFormatPolicy {
201 fn from(policy: MlsWirePolicy) -> Self {
202 match policy {
203 MlsWirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
204 MlsWirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use openmls::prelude::ProtocolVersion;
212 use openmls_traits::{
213 OpenMlsCryptoProvider,
214 crypto::OpenMlsCrypto,
215 types::{SignatureScheme, VerifiableCiphersuite},
216 };
217 use wire_e2e_identity::JwsAlgorithm;
218
219 use crate::{MlsConversationConfiguration, mls::conversation::ConversationWithMls as _, test_utils::*};
220
221 #[macro_rules_attribute::apply(smol_macros::test)]
222 async fn group_should_have_required_capabilities() {
223 let case = TestContext::default();
224
225 let [session] = case.sessions().await;
226 Box::pin(async move {
227 let conversation = case.create_conversation([&session]).await;
228 let guard = conversation.guard().await;
229 let group = guard.conversation().await;
230
231 let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
232
233 assert!(capabilities.extension_types().is_empty());
235 assert!(capabilities.proposal_types().is_empty());
236 assert_eq!(
237 capabilities.credential_types(),
238 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
239 );
240 })
241 .await
242 }
243
244 #[apply(all_cred_cipher)]
245 pub async fn creator_leaf_node_should_have_default_capabilities(case: TestContext) {
246 let [session] = case.sessions().await;
247 Box::pin(async move {
248 let conversation = case.create_conversation([&session]).await;
249 let guard = conversation.guard().await;
250 let group = guard.conversation().await;
251
252 let creator_capabilities = group.group.own_leaf().unwrap().capabilities();
254
255 assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
258
259 assert_eq!(
261 creator_capabilities.ciphersuites().to_vec(),
262 MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
263 .iter()
264 .map(|c| VerifiableCiphersuite::from(*c))
265 .collect::<Vec<_>>()
266 );
267
268 assert!(creator_capabilities.proposals().is_empty());
270
271 assert!(creator_capabilities.extensions().is_empty(),);
273
274 assert_eq!(
276 creator_capabilities.credentials(),
277 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
278 );
279 })
280 .await
281 }
282
283 #[apply(all_cred_cipher)]
284 pub async fn should_support_raw_external_sender(case: TestContext) {
285 let [cc] = case.sessions().await;
286 Box::pin(async move {
287 let (_sk, pk) = cc
288 .transaction
289 .mls_provider()
290 .await
291 .unwrap()
292 .crypto()
293 .signature_key_gen(case.signature_scheme())
294 .unwrap();
295 let pk = pk.into();
296
297 assert!(
298 case.cfg
299 .clone()
300 .set_raw_external_senders(&cc.session().await.crypto_provider, vec![pk])
301 .await
302 .is_ok()
303 );
304 })
305 .await
306 }
307
308 #[apply(all_cred_cipher)]
309 pub async fn should_support_jwk_external_sender(case: TestContext) {
310 let [cc] = case.sessions().await;
311 Box::pin(async move {
312 let sc = case.signature_scheme();
313
314 let alg = match sc {
315 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
316 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
317 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
318 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
319 SignatureScheme::ED448 => unreachable!(),
320 };
321
322 let jwk = wire_e2e_identity::generate_jwk(alg).into();
323 assert!(
324 case.cfg
325 .clone()
326 .set_raw_external_senders(&cc.session().await.crypto_provider, vec![jwk])
327 .await
328 .is_ok()
329 );
330 })
331 .await;
332 }
333}