openmls/treesync/node/leaf_node/
capabilities.rs1use std::collections::HashSet;
2
3use openmls_traits::{
4 crypto::OpenMlsCrypto,
5 types::{Ciphersuite, VerifiableCiphersuite},
6};
7use serde::{Deserialize, Serialize};
8use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};
9
10#[cfg(doc)]
11use super::LeafNode;
12use crate::{
13 credentials::CredentialType,
14 extensions::{
15 Extension, ExtensionType, ExtensionValidator, Extensions, RequiredCapabilitiesExtension,
16 },
17 messages::proposals::ProposalType,
18 treesync::errors::LeafNodeValidationError,
19 versions::ProtocolVersion,
20};
21
22#[derive(
34 Debug,
35 Clone,
36 PartialEq,
37 Eq,
38 Serialize,
39 Deserialize,
40 TlsSerialize,
41 TlsDeserialize,
42 TlsDeserializeBytes,
43 TlsSize,
44)]
45pub struct Capabilities {
46 pub(super) versions: Vec<ProtocolVersion>,
47 pub(super) ciphersuites: Vec<VerifiableCiphersuite>,
48 pub(super) extensions: Vec<ExtensionType>,
49 pub(super) proposals: Vec<ProposalType>,
50 pub(super) credentials: Vec<CredentialType>,
51}
52
53impl Capabilities {
54 pub fn new(
59 versions: Option<&[ProtocolVersion]>,
60 ciphersuites: Option<&[Ciphersuite]>,
61 extensions: Option<&[ExtensionType]>,
62 proposals: Option<&[ProposalType]>,
63 credentials: Option<&[CredentialType]>,
64 ) -> Self {
65 Self {
66 versions: match versions {
67 Some(v) => v.into(),
68 None => default_versions(),
69 },
70 ciphersuites: match ciphersuites {
71 Some(c) => c.iter().map(|c| VerifiableCiphersuite::from(*c)).collect(),
72 None => default_ciphersuites()
73 .into_iter()
74 .map(VerifiableCiphersuite::from)
75 .collect(),
76 },
77 extensions: match extensions {
78 Some(e) => e.into(),
79 None => vec![],
80 },
81 proposals: match proposals {
82 Some(p) => p.into(),
83 None => vec![],
84 },
85 credentials: match credentials {
86 Some(c) => c.into(),
87 None => default_credentials(),
88 },
89 }
90 }
91
92 pub fn empty() -> Self {
94 Self {
95 versions: Vec::new(),
96 ciphersuites: Vec::new(),
97 extensions: Vec::new(),
98 proposals: Vec::new(),
99 credentials: Vec::new(),
100 }
101 }
102
103 pub fn builder() -> CapabilitiesBuilder {
105 CapabilitiesBuilder(Self::default())
106 }
107
108 pub fn for_provider(crypto: &impl OpenMlsCrypto) -> Self {
116 Capabilities {
117 ciphersuites: crypto
118 .supported_ciphersuites()
119 .into_iter()
120 .map(VerifiableCiphersuite::from)
121 .collect(),
122 ..Default::default()
123 }
124 }
125
126 pub fn versions(&self) -> &[ProtocolVersion] {
130 &self.versions
131 }
132
133 pub fn ciphersuites(&self) -> &[VerifiableCiphersuite] {
135 &self.ciphersuites
136 }
137
138 pub fn extensions(&self) -> &[ExtensionType] {
140 &self.extensions
141 }
142
143 pub fn proposals(&self) -> &[ProposalType] {
145 &self.proposals
146 }
147
148 pub fn credentials(&self) -> &[CredentialType] {
150 &self.credentials
151 }
152
153 pub(crate) fn supports_required_capabilities(
163 &self,
164 required_capabilities: &RequiredCapabilitiesExtension,
165 ) -> Result<(), LeafNodeValidationError> {
166 let supported_extensions: HashSet<ExtensionType> =
171 self.extensions().iter().copied().collect();
172 if required_capabilities
173 .extension_types()
174 .iter()
175 .any(|e| !e.is_default() && !supported_extensions.contains(e))
176 {
177 log::error!(
178 "Leaf node does not support all required extension types\n
179 Supported extensions: {:?}\n
180 Required extensions: {:?}",
181 self.extensions(),
182 required_capabilities.extension_types()
183 );
184 return Err(LeafNodeValidationError::UnsupportedExtensions);
185 }
186 let supported_proposals: HashSet<ProposalType> = self.proposals().iter().copied().collect();
188 if required_capabilities
189 .proposal_types()
190 .iter()
191 .any(|p| !p.is_default() && !supported_proposals.contains(p))
192 {
193 return Err(LeafNodeValidationError::UnsupportedProposals);
194 }
195 let supported_credentials: HashSet<CredentialType> =
197 self.credentials().iter().copied().collect();
198 if required_capabilities
199 .credential_types()
200 .iter()
201 .any(|c| !supported_credentials.contains(c))
202 {
203 return Err(LeafNodeValidationError::UnsupportedCredentials);
204 }
205 Ok(())
206 }
207
208 pub(crate) fn contains_extensions(
210 &self,
211 extensions: &Extensions<impl ExtensionValidator>,
212 ) -> bool {
213 let mut required = extensions
214 .iter()
215 .map(Extension::extension_type)
216 .filter(|e| !e.is_default())
217 .peekable();
218
219 if required.peek().is_none() {
222 return true;
223 }
224
225 let supported: HashSet<ExtensionType> = self.extensions().iter().copied().collect();
226 required.all(|e| supported.contains(&e))
227 }
228
229 #[cfg(test)]
231 pub(crate) fn contains_extension_type(&self, extension: &ExtensionType) -> bool {
232 if extension.is_default() {
235 return true;
236 }
237
238 let supported: HashSet<ExtensionType> = self.extensions().iter().copied().collect();
239 supported.contains(extension)
240 }
241
242 pub(crate) fn contains_credential(&self, credential_type: CredentialType) -> bool {
244 self.credentials().contains(&credential_type)
245 }
246
247 pub(crate) fn contains_version(&self, version: ProtocolVersion) -> bool {
249 self.versions().contains(&version)
250 }
251
252 pub(crate) fn contains_ciphersuite(&self, ciphersuite: VerifiableCiphersuite) -> bool {
254 self.ciphersuites().contains(&ciphersuite)
255 }
256
257 pub fn with_grease(mut self, rand: &impl openmls_traits::random::OpenMlsRand) -> Self {
290 use crate::credentials::CredentialType;
291 use crate::extensions::ExtensionType;
292 use crate::messages::proposals::ProposalType;
293 use openmls_traits::types::VerifiableCiphersuite;
294
295 if !self.ciphersuites.iter().any(|cs| cs.is_grease()) {
297 let grease_cs = VerifiableCiphersuite::new(crate::grease::random_grease_value(rand));
298 self.ciphersuites.push(grease_cs);
299 }
300
301 if !self.extensions.iter().any(|ext| ext.is_grease()) {
303 let grease_ext = ExtensionType::Grease(crate::grease::random_grease_value(rand));
304 self.extensions.push(grease_ext);
305 }
306
307 if !self.proposals.iter().any(|prop| prop.is_grease()) {
309 let grease_prop = ProposalType::Grease(crate::grease::random_grease_value(rand));
310 self.proposals.push(grease_prop);
311 }
312
313 if !self.credentials.iter().any(|cred| cred.is_grease()) {
315 let grease_cred = CredentialType::Grease(crate::grease::random_grease_value(rand));
316 self.credentials.push(grease_cred);
317 }
318
319 self
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct CapabilitiesBuilder(Capabilities);
326
327impl CapabilitiesBuilder {
328 pub fn versions(self, versions: Vec<ProtocolVersion>) -> Self {
330 Self(Capabilities { versions, ..self.0 })
331 }
332
333 pub fn ciphersuites(self, ciphersuites: Vec<Ciphersuite>) -> Self {
335 let ciphersuites = ciphersuites.into_iter().map(|cs| cs.into()).collect();
336
337 Self(Capabilities {
338 ciphersuites,
339 ..self.0
340 })
341 }
342
343 pub fn extensions(self, extensions: Vec<ExtensionType>) -> Self {
345 Self(Capabilities {
346 extensions,
347 ..self.0
348 })
349 }
350
351 pub fn proposals(self, proposals: Vec<ProposalType>) -> Self {
353 Self(Capabilities {
354 proposals,
355 ..self.0
356 })
357 }
358
359 pub fn credentials(self, credentials: Vec<CredentialType>) -> Self {
361 Self(Capabilities {
362 credentials,
363 ..self.0
364 })
365 }
366
367 pub fn with_grease(self, rand: &impl openmls_traits::random::OpenMlsRand) -> Self {
388 Self(self.0.with_grease(rand))
389 }
390
391 pub fn build(self) -> Capabilities {
393 self.0
394 }
395}
396
397#[cfg(test)]
398impl Capabilities {
399 pub fn set_versions(&mut self, versions: Vec<ProtocolVersion>) {
401 self.versions = versions;
402 }
403
404 pub fn set_ciphersuites(&mut self, ciphersuites: Vec<VerifiableCiphersuite>) {
406 self.ciphersuites = ciphersuites;
407 }
408}
409
410impl Default for Capabilities {
411 fn default() -> Self {
412 Capabilities {
413 versions: default_versions(),
414 ciphersuites: default_ciphersuites()
415 .into_iter()
416 .map(VerifiableCiphersuite::from)
417 .collect(),
418 extensions: vec![],
419 proposals: vec![],
420 credentials: default_credentials(),
421 }
422 }
423}
424
425pub(super) fn default_versions() -> Vec<ProtocolVersion> {
426 vec![ProtocolVersion::Mls10]
427}
428
429pub(super) fn default_ciphersuites() -> Vec<Ciphersuite> {
430 vec![
431 Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
432 Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
433 Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
434 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
435 Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519,
436 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
437 Ciphersuite::MLS_192_MLKEM1024_AES256GCM_SHA384_P384,
438 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
439 Ciphersuite::MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA87,
440 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
441 Ciphersuite::MLS_128_MLKEM768X25519_AES256GCM_SHA384_Ed25519,
442 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
443 Ciphersuite::MLS_128_MLKEM768X25519_AES128GCM_SHA256_Ed25519,
444 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
445 Ciphersuite::MLS_128_MLKEM768_AES256GCM_SHA384_P256,
446 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
447 Ciphersuite::MLS_128_MLKEM768X25519_CHACHA20POLY1305_SHA384_MLDSA44,
448 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
449 Ciphersuite::MLS_192_MLKEM768_AES256GCM_SHA384_MLDSA65,
450 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
451 Ciphersuite::MLS_256_MLKEM1024_AES256GCM_SHA384_MLDSA87,
452 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
453 Ciphersuite::MLS_128_MLKEM768_AES256GCM_SHA384_Ed25519,
454 ]
455}
456
457pub(super) fn default_credentials() -> Vec<CredentialType> {
459 vec![CredentialType::Basic]
460}
461
462#[cfg(test)]
463mod tests {
464 use openmls_traits::{
465 crypto::OpenMlsCrypto,
466 types::{Ciphersuite, VerifiableCiphersuite},
467 };
468 use tls_codec::{Deserialize, Serialize};
469
470 use super::Capabilities;
471 use crate::{
472 credentials::CredentialType, messages::proposals::ProposalType, prelude::ExtensionType,
473 versions::ProtocolVersion,
474 };
475
476 #[test]
477 fn that_unknown_capabilities_are_de_serialized_correctly() {
478 let versions = vec![ProtocolVersion::Mls10, ProtocolVersion::Other(999)];
479 let ciphersuites = vec![
480 Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519.into(),
481 Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256.into(),
482 Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519.into(),
483 Ciphersuite::MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448.into(),
484 Ciphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521.into(),
485 Ciphersuite::MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448.into(),
486 Ciphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384.into(),
487 VerifiableCiphersuite::new(0x0000),
488 VerifiableCiphersuite::new(0x0B0B),
490 VerifiableCiphersuite::new(0x7C7C),
491 VerifiableCiphersuite::new(0xF000),
492 VerifiableCiphersuite::new(0xFFFF),
493 ];
494
495 let extensions = vec![
496 ExtensionType::Unknown(0x0000),
497 ExtensionType::Unknown(0xFAFA),
498 ];
499
500 let proposals = vec![ProposalType::Custom(0x7C7C)];
502
503 let credentials = vec![
504 CredentialType::Basic,
505 CredentialType::X509,
506 CredentialType::Other(0x0000),
507 CredentialType::Other(0x7C7C),
509 CredentialType::Other(0xFFFF),
510 ];
511
512 let expected = Capabilities {
513 versions,
514 ciphersuites,
515 extensions,
516 proposals,
517 credentials,
518 };
519
520 let test_serialized = expected.tls_serialize_detached().unwrap();
521
522 let got = Capabilities::tls_deserialize_exact(test_serialized).unwrap();
523
524 assert_eq!(expected, got);
525 }
526
527 #[test]
528 fn for_provider_advertises_exactly_the_supported_ciphersuites() {
529 let crypto = openmls_rust_crypto::RustCrypto::default();
530 let capabilities = Capabilities::for_provider(&crypto);
531
532 let expected: Vec<VerifiableCiphersuite> = crypto
533 .supported_ciphersuites()
534 .into_iter()
535 .map(VerifiableCiphersuite::from)
536 .collect();
537 assert_eq!(capabilities.ciphersuites(), expected.as_slice());
538 }
539}