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 crypto::OpenMlsCrypto,
14 types::{Ciphersuite as MlsCiphersuite, SignatureScheme},
15};
16use serde::{Deserialize, Serialize};
17use wire_e2e_identity::prelude::parse_json_jwk;
18
19use super::Result;
20use crate::{Ciphersuite, MlsError, RecursiveError};
21
22pub(crate) const MAX_PAST_EPOCHS: usize = 3;
24
25pub(crate) const OUT_OF_ORDER_TOLERANCE: u32 = 2;
28
29pub(crate) const MAXIMUM_FORWARD_DISTANCE: u32 = 1000;
31
32#[derive(Debug, Clone, Default)]
34pub struct MlsConversationConfiguration {
35 pub ciphersuite: Ciphersuite,
37 pub external_senders: Vec<ExternalSender>,
39 pub custom: MlsCustomConfiguration,
41}
42
43impl MlsConversationConfiguration {
44 const WIRE_SERVER_IDENTITY: &'static str = "wire-server";
45
46 const PADDING_SIZE: usize = 128;
47
48 pub(crate) const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::Mls10;
50
51 pub(crate) const DEFAULT_SUPPORTED_CREDENTIALS: &'static [CredentialType] =
53 &[CredentialType::Basic, CredentialType::X509];
54
55 pub(crate) const DEFAULT_SUPPORTED_CIPHERSUITES: &'static [MlsCiphersuite] = &[
57 MlsCiphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
58 MlsCiphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
59 MlsCiphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
60 MlsCiphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
61 MlsCiphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521,
62 ];
63
64 const NUMBER_RESUMPTION_PSK: usize = 1;
66
67 #[inline(always)]
69 pub fn as_openmls_default_configuration(&self) -> Result<openmls::group::MlsGroupConfig> {
70 let crypto_config = openmls::prelude::CryptoConfig {
71 version: Self::DEFAULT_PROTOCOL_VERSION,
72 ciphersuite: self.ciphersuite.into(),
73 };
74 Ok(openmls::group::MlsGroupConfig::builder()
75 .wire_format_policy(self.custom.wire_policy.into())
76 .max_past_epochs(MAX_PAST_EPOCHS)
77 .padding_size(Self::PADDING_SIZE)
78 .number_of_resumption_psks(Self::NUMBER_RESUMPTION_PSK)
79 .leaf_capabilities(Self::default_leaf_capabilities())
80 .required_capabilities(self.default_required_capabilities())
81 .sender_ratchet_configuration(SenderRatchetConfiguration::new(
82 self.custom.out_of_order_tolerance,
83 self.custom.maximum_forward_distance,
84 ))
85 .use_ratchet_tree_extension(true)
86 .external_senders(self.external_senders.clone())
87 .crypto_config(crypto_config)
88 .build())
89 }
90
91 pub fn default_leaf_capabilities() -> Capabilities {
93 Capabilities::new(
94 Some(&[Self::DEFAULT_PROTOCOL_VERSION]),
95 Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES),
96 Some(&[]),
97 Some(&[]),
98 Some(Self::DEFAULT_SUPPORTED_CREDENTIALS),
99 )
100 }
101
102 fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension {
103 RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS)
104 }
105
106 pub async fn set_raw_external_senders(
109 &mut self,
110 mls_crypto_provider: &MlsCryptoProvider,
111 external_senders: impl IntoIterator<Item = Vec<u8>>,
112 ) -> Result<()> {
113 self.external_senders = external_senders
114 .into_iter()
115 .map(|key| {
116 MlsConversationConfiguration::parse_external_sender(&key).or_else(|_| {
117 MlsConversationConfiguration::legacy_external_sender(
118 key,
119 self.ciphersuite.signature_algorithm(),
120 mls_crypto_provider,
121 )
122 })
123 })
124 .collect::<crate::mls::conversation::Result<_>>()
125 .map_err(RecursiveError::mls_conversation("setting external sender"))?;
126 Ok(())
127 }
128
129 pub(crate) fn parse_external_sender(jwk: &[u8]) -> Result<ExternalSender> {
131 let pk = parse_json_jwk(jwk)
132 .map_err(wire_e2e_identity::prelude::E2eIdentityError::from)
133 .map_err(crate::e2e_identity::Error::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::prelude::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
296 assert!(
297 case.cfg
298 .clone()
299 .set_raw_external_senders(&cc.session().await.crypto_provider, vec![pk])
300 .await
301 .is_ok()
302 );
303 })
304 .await
305 }
306
307 #[apply(all_cred_cipher)]
308 pub async fn should_support_jwk_external_sender(case: TestContext) {
309 let [cc] = case.sessions().await;
310 Box::pin(async move {
311 let sc = case.signature_scheme();
312
313 let alg = match sc {
314 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
315 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
316 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
317 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
318 SignatureScheme::ED448 => unreachable!(),
319 };
320
321 let jwk = wire_e2e_identity::prelude::generate_jwk(alg);
322 assert!(
323 case.cfg
324 .clone()
325 .set_raw_external_senders(&cc.session().await.crypto_provider, vec![jwk])
326 .await
327 .is_ok()
328 );
329 })
330 .await;
331 }
332}