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(crate) fn parse_external_sender(jwk: &[u8]) -> Result<ExternalSender> {
108 let pk = parse_json_jwk(jwk)
109 .map_err(wire_e2e_identity::prelude::E2eIdentityError::from)
110 .map_err(crate::e2e_identity::Error::from)
111 .map_err(RecursiveError::e2e_identity("parsing jwk"))?;
112 Ok(ExternalSender::new(
113 pk.into(),
114 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
115 ))
116 }
117
118 pub(crate) fn legacy_external_sender(
122 key: Vec<u8>,
123 signature_scheme: SignatureScheme,
124 backend: &MlsCryptoProvider,
125 ) -> Result<ExternalSender> {
126 backend
127 .validate_signature_key(signature_scheme, &key[..])
128 .map_err(MlsError::wrap("validating signature key"))?;
129 let key = OpenMlsSignaturePublicKey::new(key.into(), signature_scheme)
130 .map_err(MlsError::wrap("creating new signature public key"))?;
131 Ok(ExternalSender::new(
132 key.into(),
133 Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()),
134 ))
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct MlsCustomConfiguration {
141 pub key_rotation_span: Option<std::time::Duration>,
144 pub wire_policy: MlsWirePolicy,
146 pub out_of_order_tolerance: u32,
150 pub maximum_forward_distance: u32,
153}
154
155impl Default for MlsCustomConfiguration {
156 fn default() -> Self {
157 Self {
158 wire_policy: MlsWirePolicy::Plaintext,
159 key_rotation_span: Default::default(),
160 out_of_order_tolerance: OUT_OF_ORDER_TOLERANCE,
161 maximum_forward_distance: MAXIMUM_FORWARD_DISTANCE,
162 }
163 }
164}
165
166#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
168#[repr(u8)]
169pub enum MlsWirePolicy {
170 #[default]
172 Plaintext = 1,
173 Ciphertext = 2,
175}
176
177impl From<MlsWirePolicy> for WireFormatPolicy {
178 fn from(policy: MlsWirePolicy) -> Self {
179 match policy {
180 MlsWirePolicy::Ciphertext => PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
181 MlsWirePolicy::Plaintext => PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use openmls::prelude::ProtocolVersion;
189 use openmls_traits::{
190 OpenMlsCryptoProvider,
191 crypto::OpenMlsCrypto,
192 types::{SignatureScheme, VerifiableCiphersuite},
193 };
194 use wire_e2e_identity::prelude::JwsAlgorithm;
195
196 use crate::{MlsConversationConfiguration, mls::conversation::ConversationWithMls as _, test_utils::*};
197
198 #[macro_rules_attribute::apply(smol_macros::test)]
199 async fn group_should_have_required_capabilities() {
200 let case = TestContext::default();
201
202 let [session] = case.sessions().await;
203 Box::pin(async move {
204 let conversation = case.create_conversation([&session]).await;
205 let guard = conversation.guard().await;
206 let group = guard.conversation().await;
207
208 let capabilities = group.group.group_context_extensions().required_capabilities().unwrap();
209
210 assert!(capabilities.extension_types().is_empty());
212 assert!(capabilities.proposal_types().is_empty());
213 assert_eq!(
214 capabilities.credential_types(),
215 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
216 );
217 })
218 .await
219 }
220
221 #[apply(all_cred_cipher)]
222 pub async fn creator_leaf_node_should_have_default_capabilities(case: TestContext) {
223 let [session] = case.sessions().await;
224 Box::pin(async move {
225 let conversation = case.create_conversation([&session]).await;
226 let guard = conversation.guard().await;
227 let group = guard.conversation().await;
228
229 let creator_capabilities = group.group.own_leaf().unwrap().capabilities();
231
232 assert_eq!(creator_capabilities.versions(), &[ProtocolVersion::Mls10]);
235
236 assert_eq!(
238 creator_capabilities.ciphersuites().to_vec(),
239 MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
240 .iter()
241 .map(|c| VerifiableCiphersuite::from(*c))
242 .collect::<Vec<_>>()
243 );
244
245 assert!(creator_capabilities.proposals().is_empty());
247
248 assert!(creator_capabilities.extensions().is_empty(),);
250
251 assert_eq!(
253 creator_capabilities.credentials(),
254 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
255 );
256 })
257 .await
258 }
259
260 #[apply(all_cred_cipher)]
261 pub async fn should_support_raw_external_sender(case: TestContext) {
262 let [cc] = case.sessions().await;
263 Box::pin(async move {
264 let (_sk, pk) = cc
265 .transaction
266 .mls_provider()
267 .await
268 .unwrap()
269 .crypto()
270 .signature_key_gen(case.signature_scheme())
271 .unwrap();
272
273 assert!(
274 cc.transaction
275 .set_raw_external_senders(&mut case.cfg.clone(), vec![pk])
276 .await
277 .is_ok()
278 );
279 })
280 .await
281 }
282
283 #[apply(all_cred_cipher)]
284 pub async fn should_support_jwk_external_sender(case: TestContext) {
285 let [cc] = case.sessions().await;
286 Box::pin(async move {
287 let sc = case.signature_scheme();
288
289 let alg = match sc {
290 SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
291 SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
292 SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
293 SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
294 SignatureScheme::ED448 => unreachable!(),
295 };
296
297 let jwk = wire_e2e_identity::prelude::generate_jwk(alg);
298 cc.transaction
299 .set_raw_external_senders(&mut case.cfg.clone(), vec![jwk])
300 .await
301 .unwrap();
302 })
303 .await;
304 }
305}