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::{Ciphersuite, MlsError, RecursiveError, mls_provider::MlsCryptoProvider};
20
21pub(crate) const MAX_PAST_EPOCHS: usize = 3;
23
24pub(crate) const OUT_OF_ORDER_TOLERANCE: u32 = 2;
27
28pub(crate) const MAXIMUM_FORWARD_DISTANCE: u32 = 1000;
30
31#[derive(Debug, Clone, Default)]
33pub struct MlsConversationConfiguration {
34 pub ciphersuite: Ciphersuite,
36 pub external_senders: Vec<ExternalSender>,
38 pub custom: MlsCustomConfiguration,
40}
41
42impl MlsConversationConfiguration {
43 const WIRE_SERVER_IDENTITY: &'static str = "wire-server";
44
45 const PADDING_SIZE: usize = 128;
46
47 pub(crate) const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::Mls10;
49
50 pub(crate) const DEFAULT_SUPPORTED_CREDENTIALS: &'static [CredentialType] =
52 &[CredentialType::Basic, CredentialType::X509];
53
54 pub(crate) const DEFAULT_SUPPORTED_CIPHERSUITES: &'static [MlsCiphersuite] = &[
56 MlsCiphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
57 MlsCiphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
58 MlsCiphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
59 MlsCiphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384,
60 MlsCiphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521,
61 ];
62
63 const NUMBER_RESUMPTION_PSK: usize = 1;
65
66 #[inline(always)]
68 pub fn as_openmls_default_configuration(&self) -> Result<openmls::group::MlsGroupConfig> {
69 let crypto_config = openmls::prelude::CryptoConfig {
70 version: Self::DEFAULT_PROTOCOL_VERSION,
71 ciphersuite: self.ciphersuite.into(),
72 };
73 Ok(openmls::group::MlsGroupConfig::builder()
74 .wire_format_policy(self.custom.wire_policy.into())
75 .max_past_epochs(MAX_PAST_EPOCHS)
76 .padding_size(Self::PADDING_SIZE)
77 .number_of_resumption_psks(Self::NUMBER_RESUMPTION_PSK)
78 .leaf_capabilities(Self::default_leaf_capabilities())
79 .required_capabilities(self.default_required_capabilities())
80 .sender_ratchet_configuration(SenderRatchetConfiguration::new(
81 self.custom.out_of_order_tolerance,
82 self.custom.maximum_forward_distance,
83 ))
84 .use_ratchet_tree_extension(true)
85 .external_senders(self.external_senders.clone())
86 .crypto_config(crypto_config)
87 .build())
88 }
89
90 pub fn default_leaf_capabilities() -> Capabilities {
92 Capabilities::new(
93 Some(&[Self::DEFAULT_PROTOCOL_VERSION]),
94 Some(Self::DEFAULT_SUPPORTED_CIPHERSUITES),
95 Some(&[]),
96 Some(&[]),
97 Some(Self::DEFAULT_SUPPORTED_CREDENTIALS),
98 )
99 }
100
101 fn default_required_capabilities(&self) -> RequiredCapabilitiesExtension {
102 RequiredCapabilitiesExtension::new(&[], &[], Self::DEFAULT_SUPPORTED_CREDENTIALS)
103 }
104
105 pub async fn set_raw_external_senders(
108 &mut self,
109 mls_crypto_provider: &MlsCryptoProvider,
110 external_senders: impl IntoIterator<Item = Vec<u8>>,
111 ) -> Result<()> {
112 self.external_senders = external_senders
113 .into_iter()
114 .map(|key| {
115 MlsConversationConfiguration::parse_external_sender(&key).or_else(|_| {
116 MlsConversationConfiguration::legacy_external_sender(
117 key,
118 self.ciphersuite.signature_algorithm(),
119 mls_crypto_provider,
120 )
121 })
122 })
123 .collect::<crate::mls::conversation::Result<_>>()
124 .map_err(RecursiveError::mls_conversation("setting external sender"))?;
125 Ok(())
126 }
127
128 pub(crate) fn parse_external_sender(jwk: &[u8]) -> Result<ExternalSender> {
130 let pk = parse_json_jwk(jwk)
131 .map_err(wire_e2e_identity::E2eIdentityError::from)
132 .map_err(crate::e2e_identity::Error::from)
133 .map_err(RecursiveError::e2e_identity("parsing jwk"))?;
134 Ok(ExternalSender::new(
135 pk.into(),
136 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
137 ))
138 }
139
140 pub(crate) fn legacy_external_sender(
144 key: Vec<u8>,
145 signature_scheme: SignatureScheme,
146 backend: &MlsCryptoProvider,
147 ) -> Result<ExternalSender> {
148 backend
149 .validate_signature_key(signature_scheme, &key[..])
150 .map_err(MlsError::wrap("validating signature key"))?;
151 let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme)
152 .map_err(MlsError::wrap("creating new signature public key"))?;
153 Ok(ExternalSender::new(
154 key.into(),
155 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
156 ))
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct MlsCustomConfiguration {
163 pub key_rotation_span: Option<std::time::Duration>,
166 pub wire_policy: MlsWirePolicy,
168 pub out_of_order_tolerance: u32,
172 pub maximum_forward_distance: u32,
175}
176
177impl Default for MlsCustomConfiguration {
178 fn default() -> Self {
179 Self {
180 wire_policy: MlsWirePolicy::Plaintext,
181 key_rotation_span: Default::default(),
182 out_of_order_tolerance: OUT_OF_ORDER_TOLERANCE,
183 maximum_forward_distance: MAXIMUM_FORWARD_DISTANCE,
184 }
185 }
186}
187
188#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
190#[repr(u8)]
191pub enum MlsWirePolicy {
192 #[default]
194 Plaintext = 1,
195 Ciphertext = 2,
197}
198
199impl From<MlsWirePolicy> for WireFormatPolicy {
200 fn from(policy: MlsWirePolicy) -> Self {
201 match policy {
202 MlsWirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
203 MlsWirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use openmls::prelude::ProtocolVersion;
211 use openmls_traits::{
212 OpenMlsCryptoProvider,
213 crypto::OpenMlsCrypto,
214 types::{SignatureScheme, VerifiableCiphersuite},
215 };
216 use wire_e2e_identity::JwsAlgorithm;
217
218 use crate::{MlsConversationConfiguration, mls::conversation::ConversationWithMls as _, test_utils::*};
219
220 #[macro_rules_attribute::apply(smol_macros::test)]
221 async fn group_should_have_required_capabilities() {
222 let case = TestContext::default();
223
224 let [session] = case.sessions().await;
225 Box::pin(async move {
226 let conversation = case.create_conversation([&session]).await;
227 let guard = conversation.guard().await;
228 let group = guard.conversation().await;
229
230 let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
231
232 assert!(capabilities.extension_types().is_empty());
234 assert!(capabilities.proposal_types().is_empty());
235 assert_eq!(
236 capabilities.credential_types(),
237 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
238 );
239 })
240 .await
241 }
242
243 #[apply(all_cred_cipher)]
244 pub async fn creator_leaf_node_should_have_default_capabilities(case: TestContext) {
245 let [session] = case.sessions().await;
246 Box::pin(async move {
247 let conversation = case.create_conversation([&session]).await;
248 let guard = conversation.guard().await;
249 let group = guard.conversation().await;
250
251 let creator_capabilities = group.group.own_leaf().unwrap().capabilities();
253
254 assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
257
258 assert_eq!(
260 creator_capabilities.ciphersuites().to_vec(),
261 MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
262 .iter()
263 .map(|c| VerifiableCiphersuite::from(*c))
264 .collect::<Vec<_>>()
265 );
266
267 assert!(creator_capabilities.proposals().is_empty());
269
270 assert!(creator_capabilities.extensions().is_empty(),);
272
273 assert_eq!(
275 creator_capabilities.credentials(),
276 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
277 );
278 })
279 .await
280 }
281
282 #[apply(all_cred_cipher)]
283 pub async fn should_support_raw_external_sender(case: TestContext) {
284 let [cc] = case.sessions().await;
285 Box::pin(async move {
286 let (_sk, pk) = cc
287 .transaction
288 .mls_provider()
289 .await
290 .unwrap()
291 .crypto()
292 .signature_key_gen(case.signature_scheme())
293 .unwrap();
294
295 assert!(
296 case.cfg
297 .clone()
298 .set_raw_external_senders(&cc.session().await.crypto_provider, vec![pk])
299 .await
300 .is_ok()
301 );
302 })
303 .await
304 }
305
306 #[apply(all_cred_cipher)]
307 pub async fn should_support_jwk_external_sender(case: TestContext) {
308 let [cc] = case.sessions().await;
309 Box::pin(async move {
310 let sc = case.signature_scheme();
311
312 let alg = match sc {
313 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
314 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
315 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
316 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
317 SignatureScheme::ED448 => unreachable!(),
318 };
319
320 let jwk = wire_e2e_identity::generate_jwk(alg);
321 assert!(
322 case.cfg
323 .clone()
324 .set_raw_external_senders(&cc.session().await.crypto_provider, vec![jwk])
325 .await
326 .is_ok()
327 );
328 })
329 .await;
330 }
331}