openmls/treesync/node/leaf_node/
capabilities.rs1use openmls_traits::{
2 crypto::OpenMlsCrypto,
3 types::{Ciphersuite, VerifiableCiphersuite},
4};
5use serde::{Deserialize, Serialize};
6use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};
7
8#[cfg(doc)]
9use super::LeafNode;
10use crate::{
11 credentials::CredentialType,
12 extensions::{
13 Extension, ExtensionType, ExtensionValidator, Extensions, RequiredCapabilitiesExtension,
14 },
15 messages::proposals::ProposalType,
16 treesync::errors::LeafNodeValidationError,
17 versions::ProtocolVersion,
18};
19
20#[derive(
32 Debug,
33 Clone,
34 PartialEq,
35 Eq,
36 Serialize,
37 Deserialize,
38 TlsSerialize,
39 TlsDeserialize,
40 TlsDeserializeBytes,
41 TlsSize,
42)]
43pub struct Capabilities {
44 pub(super) versions: Vec<ProtocolVersion>,
45 pub(super) ciphersuites: Vec<VerifiableCiphersuite>,
46 pub(super) extensions: Vec<ExtensionType>,
47 pub(super) proposals: Vec<ProposalType>,
48 pub(super) credentials: Vec<CredentialType>,
49}
50
51impl Capabilities {
52 pub fn new(
57 versions: Option<&[ProtocolVersion]>,
58 ciphersuites: Option<&[Ciphersuite]>,
59 extensions: Option<&[ExtensionType]>,
60 proposals: Option<&[ProposalType]>,
61 credentials: Option<&[CredentialType]>,
62 ) -> Self {
63 Self {
64 versions: match versions {
65 Some(v) => v.into(),
66 None => default_versions(),
67 },
68 ciphersuites: match ciphersuites {
69 Some(c) => c.iter().map(|c| VerifiableCiphersuite::from(*c)).collect(),
70 None => default_ciphersuites()
71 .into_iter()
72 .map(VerifiableCiphersuite::from)
73 .collect(),
74 },
75 extensions: match extensions {
76 Some(e) => e.into(),
77 None => vec![],
78 },
79 proposals: match proposals {
80 Some(p) => p.into(),
81 None => vec![],
82 },
83 credentials: match credentials {
84 Some(c) => c.into(),
85 None => default_credentials(),
86 },
87 }
88 }
89
90 pub fn empty() -> Self {
92 Self {
93 versions: Vec::new(),
94 ciphersuites: Vec::new(),
95 extensions: Vec::new(),
96 proposals: Vec::new(),
97 credentials: Vec::new(),
98 }
99 }
100
101 pub fn builder() -> CapabilitiesBuilder {
103 CapabilitiesBuilder(Self::default())
104 }
105
106 pub fn for_provider(crypto: &impl OpenMlsCrypto) -> Self {
114 Capabilities {
115 ciphersuites: crypto
116 .supported_ciphersuites()
117 .into_iter()
118 .map(VerifiableCiphersuite::from)
119 .collect(),
120 ..Default::default()
121 }
122 }
123
124 pub fn versions(&self) -> &[ProtocolVersion] {
128 &self.versions
129 }
130
131 pub fn ciphersuites(&self) -> &[VerifiableCiphersuite] {
133 &self.ciphersuites
134 }
135
136 pub fn extensions(&self) -> &[ExtensionType] {
138 &self.extensions
139 }
140
141 pub fn proposals(&self) -> &[ProposalType] {
143 &self.proposals
144 }
145
146 pub fn credentials(&self) -> &[CredentialType] {
148 &self.credentials
149 }
150
151 pub(crate) fn supports_required_capabilities(
161 &self,
162 required_capabilities: &RequiredCapabilitiesExtension,
163 ) -> Result<(), LeafNodeValidationError> {
164 let unsupported_extension_types = required_capabilities
166 .extension_types()
167 .iter()
168 .filter(|&e| !self.contains_extension(*e))
169 .collect::<Vec<_>>();
170 if !unsupported_extension_types.is_empty() {
171 log::error!(
172 "Leaf node does not support all required extension types\n
173 Supported extensions: {:?}\n
174 Required extensions: {:?}",
175 self.extensions(),
176 required_capabilities.extension_types()
177 );
178 return Err(LeafNodeValidationError::UnsupportedExtensions);
179 }
180 if required_capabilities
182 .proposal_types()
183 .iter()
184 .any(|p| !self.contains_proposal(*p))
185 {
186 return Err(LeafNodeValidationError::UnsupportedProposals);
187 }
188 if required_capabilities
190 .credential_types()
191 .iter()
192 .any(|c| !self.contains_credential(*c))
193 {
194 return Err(LeafNodeValidationError::UnsupportedCredentials);
195 }
196 Ok(())
197 }
198
199 pub(crate) fn contains_extensions(
201 &self,
202 extensions: &Extensions<impl ExtensionValidator>,
203 ) -> bool {
204 extensions
205 .iter()
206 .map(Extension::extension_type)
207 .all(|e| e.is_default() || self.extensions().contains(&e))
208 }
209
210 pub(crate) fn contains_credential(&self, credential_type: CredentialType) -> bool {
212 self.credentials().contains(&credential_type)
213 }
214
215 pub(crate) fn contains_extension(&self, extension_type: ExtensionType) -> bool {
217 extension_type.is_default() || self.extensions().contains(&extension_type)
218 }
219
220 pub(crate) fn contains_proposal(&self, proposal_type: ProposalType) -> bool {
222 proposal_type.is_default() || self.proposals().contains(&proposal_type)
223 }
224
225 pub(crate) fn contains_version(&self, version: ProtocolVersion) -> bool {
227 self.versions().contains(&version)
228 }
229
230 pub(crate) fn contains_ciphersuite(&self, ciphersuite: VerifiableCiphersuite) -> bool {
232 self.ciphersuites().contains(&ciphersuite)
233 }
234
235 pub fn with_grease(mut self, rand: &impl openmls_traits::random::OpenMlsRand) -> Self {
268 use crate::credentials::CredentialType;
269 use crate::extensions::ExtensionType;
270 use crate::messages::proposals::ProposalType;
271 use openmls_traits::types::VerifiableCiphersuite;
272
273 if !self.ciphersuites.iter().any(|cs| cs.is_grease()) {
275 let grease_cs = VerifiableCiphersuite::new(crate::grease::random_grease_value(rand));
276 self.ciphersuites.push(grease_cs);
277 }
278
279 if !self.extensions.iter().any(|ext| ext.is_grease()) {
281 let grease_ext = ExtensionType::Grease(crate::grease::random_grease_value(rand));
282 self.extensions.push(grease_ext);
283 }
284
285 if !self.proposals.iter().any(|prop| prop.is_grease()) {
287 let grease_prop = ProposalType::Grease(crate::grease::random_grease_value(rand));
288 self.proposals.push(grease_prop);
289 }
290
291 if !self.credentials.iter().any(|cred| cred.is_grease()) {
293 let grease_cred = CredentialType::Grease(crate::grease::random_grease_value(rand));
294 self.credentials.push(grease_cred);
295 }
296
297 self
298 }
299}
300
301#[derive(Debug, Clone)]
303pub struct CapabilitiesBuilder(Capabilities);
304
305impl CapabilitiesBuilder {
306 pub fn versions(self, versions: Vec<ProtocolVersion>) -> Self {
308 Self(Capabilities { versions, ..self.0 })
309 }
310
311 pub fn ciphersuites(self, ciphersuites: Vec<Ciphersuite>) -> Self {
313 let ciphersuites = ciphersuites.into_iter().map(|cs| cs.into()).collect();
314
315 Self(Capabilities {
316 ciphersuites,
317 ..self.0
318 })
319 }
320
321 pub fn extensions(self, extensions: Vec<ExtensionType>) -> Self {
323 Self(Capabilities {
324 extensions,
325 ..self.0
326 })
327 }
328
329 pub fn proposals(self, proposals: Vec<ProposalType>) -> Self {
331 Self(Capabilities {
332 proposals,
333 ..self.0
334 })
335 }
336
337 pub fn credentials(self, credentials: Vec<CredentialType>) -> Self {
339 Self(Capabilities {
340 credentials,
341 ..self.0
342 })
343 }
344
345 pub fn with_grease(self, rand: &impl openmls_traits::random::OpenMlsRand) -> Self {
366 Self(self.0.with_grease(rand))
367 }
368
369 pub fn build(self) -> Capabilities {
371 self.0
372 }
373}
374
375#[cfg(test)]
376impl Capabilities {
377 pub fn set_versions(&mut self, versions: Vec<ProtocolVersion>) {
379 self.versions = versions;
380 }
381
382 pub fn set_ciphersuites(&mut self, ciphersuites: Vec<VerifiableCiphersuite>) {
384 self.ciphersuites = ciphersuites;
385 }
386}
387
388impl Default for Capabilities {
389 fn default() -> Self {
390 Capabilities {
391 versions: default_versions(),
392 ciphersuites: default_ciphersuites()
393 .into_iter()
394 .map(VerifiableCiphersuite::from)
395 .collect(),
396 extensions: vec![],
397 proposals: vec![],
398 credentials: default_credentials(),
399 }
400 }
401}
402
403pub(super) fn default_versions() -> Vec<ProtocolVersion> {
404 vec![ProtocolVersion::Mls10]
405}
406
407pub(super) fn default_ciphersuites() -> Vec<Ciphersuite> {
408 vec![
409 Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
410 Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256,
411 Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519,
412 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
413 Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519,
414 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
415 Ciphersuite::MLS_192_MLKEM1024_AES256GCM_SHA384_P384,
416 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
417 Ciphersuite::MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA87,
418 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
419 Ciphersuite::MLS_128_MLKEM768X25519_AES256GCM_SHA384_Ed25519,
420 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
421 Ciphersuite::MLS_128_MLKEM768X25519_AES128GCM_SHA256_Ed25519,
422 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
423 Ciphersuite::MLS_128_MLKEM768_AES256GCM_SHA384_P256,
424 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
425 Ciphersuite::MLS_128_MLKEM768X25519_CHACHA20POLY1305_SHA384_MLDSA44,
426 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
427 Ciphersuite::MLS_192_MLKEM768_AES256GCM_SHA384_MLDSA65,
428 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
429 Ciphersuite::MLS_256_MLKEM1024_AES256GCM_SHA384_MLDSA87,
430 #[cfg(feature = "draft-ietf-mls-pq-ciphersuites")]
431 Ciphersuite::MLS_128_MLKEM768_AES256GCM_SHA384_Ed25519,
432 ]
433}
434
435pub(super) fn default_credentials() -> Vec<CredentialType> {
437 vec![CredentialType::Basic]
438}
439
440#[cfg(test)]
441mod tests {
442 use openmls_traits::{
443 crypto::OpenMlsCrypto,
444 types::{Ciphersuite, VerifiableCiphersuite},
445 };
446 use tls_codec::{Deserialize, Serialize};
447
448 use super::Capabilities;
449 use crate::{
450 credentials::CredentialType, messages::proposals::ProposalType, prelude::ExtensionType,
451 versions::ProtocolVersion,
452 };
453
454 #[test]
455 fn that_unknown_capabilities_are_de_serialized_correctly() {
456 let versions = vec![ProtocolVersion::Mls10, ProtocolVersion::Other(999)];
457 let ciphersuites = vec![
458 Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519.into(),
459 Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256.into(),
460 Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519.into(),
461 Ciphersuite::MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448.into(),
462 Ciphersuite::MLS_256_DHKEMP521_AES256GCM_SHA512_P521.into(),
463 Ciphersuite::MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448.into(),
464 Ciphersuite::MLS_256_DHKEMP384_AES256GCM_SHA384_P384.into(),
465 VerifiableCiphersuite::new(0x0000),
466 VerifiableCiphersuite::new(0x0B0B),
468 VerifiableCiphersuite::new(0x7C7C),
469 VerifiableCiphersuite::new(0xF000),
470 VerifiableCiphersuite::new(0xFFFF),
471 ];
472
473 let extensions = vec![
474 ExtensionType::Unknown(0x0000),
475 ExtensionType::Unknown(0xFAFA),
476 ];
477
478 let proposals = vec![ProposalType::Custom(0x7C7C)];
480
481 let credentials = vec![
482 CredentialType::Basic,
483 CredentialType::X509,
484 CredentialType::Other(0x0000),
485 CredentialType::Other(0x7C7C),
487 CredentialType::Other(0xFFFF),
488 ];
489
490 let expected = Capabilities {
491 versions,
492 ciphersuites,
493 extensions,
494 proposals,
495 credentials,
496 };
497
498 let test_serialized = expected.tls_serialize_detached().unwrap();
499
500 let got = Capabilities::tls_deserialize_exact(test_serialized).unwrap();
501
502 assert_eq!(expected, got);
503 }
504
505 #[test]
506 fn for_provider_advertises_exactly_the_supported_ciphersuites() {
507 let crypto = openmls_rust_crypto::RustCrypto::default();
508 let capabilities = Capabilities::for_provider(&crypto);
509
510 let expected: Vec<VerifiableCiphersuite> = crypto
511 .supported_ciphersuites()
512 .into_iter()
513 .map(VerifiableCiphersuite::from)
514 .collect();
515 assert_eq!(capabilities.ciphersuites(), expected.as_slice());
516 }
517}