Skip to main content

openmls/treesync/node/
leaf_node.rs

1//! This module contains the [`LeafNode`] struct and its implementation.
2use std::collections::HashSet;
3
4use openmls_traits::{
5    crypto::OpenMlsCrypto,
6    random::OpenMlsRand,
7    signatures::Signer,
8    types::{Ciphersuite, VerifiableCiphersuite},
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12use tls_codec::{
13    Serialize as TlsSerializeTrait, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
14    VLBytes,
15};
16
17use super::encryption_keys::{EncryptionKey, EncryptionKeyPair};
18use crate::{
19    binary_tree::array_representation::LeafNodeIndex,
20    ciphersuite::{
21        signable::{Signable, SignedStruct, Verifiable, VerifiedStruct},
22        Signature, SignaturePublicKey,
23    },
24    credentials::{Credential, CredentialType, CredentialWithKey},
25    error::LibraryError,
26    extensions::{Extension, ExtensionType, Extensions, RequiredCapabilitiesExtension},
27    group::{GroupContext, GroupId},
28    key_packages::{KeyPackage, Lifetime},
29    prelude::KeyPackageBundle,
30    storage::OpenMlsProvider,
31    versions::ProtocolVersion,
32};
33
34use crate::treesync::errors::LeafNodeValidationError;
35
36mod capabilities;
37mod codec;
38
39pub use capabilities::*;
40
41pub(crate) struct NewLeafNodeParams {
42    pub(crate) ciphersuite: Ciphersuite,
43    pub(crate) credential_with_key: CredentialWithKey,
44    pub(crate) leaf_node_source: LeafNodeSource,
45    pub(crate) capabilities: Capabilities,
46    pub(crate) extensions: Extensions<LeafNode>,
47    pub(crate) tree_info_tbs: TreeInfoTbs,
48    /// What the group the leaf is built for requires of it. Empty for a
49    /// `KeyPackage`, which has no group.
50    pub(crate) constraints: LeafNodeConstraints,
51    /// How `capabilities` is treated when it doesn't cover what the leaf
52    /// needs. See [`LeafNodePayload::enforce_capabilities`].
53    pub(crate) capabilities_policy: CapabilitiesPolicy,
54}
55
56/// Set of LeafNode parameters that are used when regenerating a LeafNodes
57/// during an update operation.
58#[derive(Debug, PartialEq, Clone)]
59pub(crate) struct UpdateLeafNodeParams {
60    pub(crate) credential_with_key: CredentialWithKey,
61    pub(crate) capabilities: Capabilities,
62    pub(crate) extensions: Extensions<LeafNode>,
63    /// What the group the leaf is built for requires.
64    pub(crate) constraints: LeafNodeConstraints,
65    /// How `capabilities` is treated when it doesn't cover what the leaf
66    /// needs. See [`LeafNodePayload::enforce_capabilities`].
67    pub(crate) capabilities_policy: CapabilitiesPolicy,
68}
69
70impl UpdateLeafNodeParams {
71    #[cfg(test)]
72    pub(crate) fn derive(leaf_node: &LeafNode) -> Self {
73        Self {
74            credential_with_key: CredentialWithKey {
75                credential: leaf_node.payload.credential.clone(),
76                signature_key: leaf_node.payload.signature_key.clone(),
77            },
78            capabilities: leaf_node.payload.capabilities.clone(),
79            extensions: leaf_node.payload.extensions.clone(),
80            // This reconstructs an already-consistent leaf; there's no group
81            // available here to source constraints from.
82            constraints: LeafNodeConstraints::default(),
83            capabilities_policy: CapabilitiesPolicy::Reject,
84        }
85    }
86}
87
88/// We need to build a leaf node that's compatible with the group.
89///
90/// These are the checks a receiving member applies to the leaf's capabilities
91/// (see [`PublicGroup::validate_leaf_node`] and the GroupContext extension
92/// checks), so a leaf that passes them is valid for the group.
93///
94/// The default requires nothing, which is used for [`KeyPackage`].
95///
96/// [`PublicGroup::validate_leaf_node`]: crate::group::PublicGroup::validate_leaf_node
97#[derive(Debug, Clone, Default, PartialEq)]
98pub(crate) struct LeafNodeConstraints {
99    /// Commits may change the required capabilities.
100    /// The leaf must support all of them.
101    required_capabilities: Vec<RequiredCapabilitiesExtension>,
102
103    /// Extension types currently present in the group context.
104    /// The leaf must support all of them.
105    group_context_extensions: HashSet<ExtensionType>,
106
107    /// Credential types of the members.
108    /// The leaf must support all of them.
109    credentials_in_use: HashSet<CredentialType>,
110
111    /// Credential types every member supports.
112    /// The leaf's own credential must be one of them.
113    /// Empty, i.e. no restriction, while there are no members (the group
114    /// creator, a key package). Never empty with members, since each
115    /// supports every credential type in use (valn0104).
116    credentials_supported_by_all: HashSet<CredentialType>,
117}
118
119impl LeafNodeConstraints {
120    /// Constraints from a group's GroupContext extensions alone, e.g. for the
121    /// creator of a group that has no other members yet.
122    pub(crate) fn from_group_context_extensions(extensions: &Extensions<GroupContext>) -> Self {
123        Self::new(extensions, [])
124    }
125
126    /// Constraints from a group's GroupContext extensions and its `members`.
127    pub(crate) fn new<'a>(
128        extensions: &Extensions<GroupContext>,
129        members: impl IntoIterator<Item = &'a LeafNode>,
130    ) -> Self {
131        let mut credentials_in_use = HashSet::new();
132        let mut credentials_supported_by_all = None;
133
134        for member in members {
135            credentials_in_use.insert(member.credential().credential_type());
136            let supported = member.capabilities().credentials();
137            credentials_supported_by_all
138                .get_or_insert_with(|| supported.iter().copied().collect::<HashSet<_>>())
139                .retain(|credential_type| supported.contains(credential_type));
140        }
141
142        let mut constraints = Self {
143            credentials_in_use,
144            credentials_supported_by_all: credentials_supported_by_all.unwrap_or_default(),
145            ..Self::default()
146        };
147        constraints.add_group_context_extensions(extensions);
148
149        constraints
150    }
151
152    /// Also demand support for `extensions` and their required capabilities.
153    pub(crate) fn add_group_context_extensions(&mut self, extensions: &Extensions<GroupContext>) {
154        if let Some(required_capabilities) = extensions.required_capabilities() {
155            self.required_capabilities
156                .push(required_capabilities.clone());
157        }
158        self.group_context_extensions
159            .extend(extensions.iter().map(Extension::extension_type));
160    }
161
162    /// Check `capabilities` against the constraints.
163    fn check(
164        &self,
165        capabilities: &Capabilities,
166        credential_type: CredentialType,
167    ) -> Result<(), LeafNodeValidationError> {
168        // https://validation.openmls.tech/#valn0103
169        for required_capabilities in &self.required_capabilities {
170            capabilities.supports_required_capabilities(required_capabilities)?;
171        }
172
173        // https://validation.openmls.tech/#valn0602
174        // https://validation.openmls.tech/#valn1210
175        if self.group_context_extensions.iter().any(|extension_type| {
176            !extension_type.is_default() && !capabilities.extensions().contains(extension_type)
177        }) {
178            return Err(LeafNodeValidationError::UnsupportedExtensions);
179        }
180
181        // https://validation.openmls.tech/#valn0104
182        if !self.credentials_supported_by_all.is_empty()
183            && !self.credentials_supported_by_all.contains(&credential_type)
184        {
185            return Err(LeafNodeValidationError::LeafNodeCredentialNotSupportedByMember);
186        }
187        if self
188            .credentials_in_use
189            .iter()
190            .any(|in_use| !capabilities.contains_credential(*in_use))
191        {
192            return Err(LeafNodeValidationError::MemberCredentialNotSupportedByLeafNode);
193        }
194
195        Ok(())
196    }
197}
198
199/// Parameters for a leaf node that can be chosen by the application.
200#[derive(Debug, PartialEq, Clone, Default)]
201pub struct LeafNodeParameters {
202    credential_with_key: Option<CredentialWithKey>,
203    capabilities: Option<Capabilities>,
204    extensions: Option<Extensions<LeafNode>>,
205    capabilities_policy: Option<CapabilitiesPolicy>,
206}
207
208impl LeafNodeParameters {
209    /// Create a new [`LeafNodeParametersBuilder`].
210    pub fn builder() -> LeafNodeParametersBuilder {
211        LeafNodeParametersBuilder::default()
212    }
213
214    /// Returns the credential with key.
215    pub fn credential_with_key(&self) -> Option<&CredentialWithKey> {
216        self.credential_with_key.as_ref()
217    }
218
219    /// Returns the capabilities.
220    pub fn capabilities(&self) -> Option<&Capabilities> {
221        self.capabilities.as_ref()
222    }
223
224    /// Returns the extensions.
225    pub fn extensions(&self) -> Option<&Extensions<LeafNode>> {
226        self.extensions.as_ref()
227    }
228
229    /// Returns the capabilities policy the caller set, if any. `None` lets
230    /// [`resolve_capabilities`] pick, which depends on whether `capabilities`
231    /// was set as well.
232    pub(crate) fn capabilities_policy(&self) -> Option<CapabilitiesPolicy> {
233        self.capabilities_policy
234    }
235
236    pub(crate) fn is_empty(&self) -> bool {
237        self.credential_with_key.is_none()
238            && self.capabilities.is_none()
239            && self.extensions.is_none()
240    }
241
242    pub(crate) fn set_credential_with_key(&mut self, credential_with_key: CredentialWithKey) {
243        self.credential_with_key = Some(credential_with_key);
244    }
245
246    #[cfg(feature = "virtual-clients-draft")]
247    pub(crate) fn set_extensions(&mut self, extensions: Extensions<LeafNode>) {
248        self.extensions = Some(extensions);
249    }
250}
251
252/// Builder for [`LeafNodeParameters`].
253#[derive(Debug, Default)]
254pub struct LeafNodeParametersBuilder {
255    credential_with_key: Option<CredentialWithKey>,
256    capabilities: Option<Capabilities>,
257    extensions: Option<Extensions<LeafNode>>,
258    capabilities_policy: Option<CapabilitiesPolicy>,
259}
260
261impl LeafNodeParametersBuilder {
262    /// Set the credential with key.
263    pub fn with_credential_with_key(mut self, credential_with_key: CredentialWithKey) -> Self {
264        self.credential_with_key = Some(credential_with_key);
265        self
266    }
267
268    /// Set the capabilities.
269    pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
270        self.capabilities = Some(capabilities);
271        self
272    }
273
274    /// Set the extensions.
275    ///
276    /// Returns an error if one or more of the extensions is invalid in leaf nodes.
277    pub fn with_extensions(mut self, extensions: Extensions<LeafNode>) -> Self {
278        self.extensions = Some(extensions);
279        self
280    }
281
282    /// Set how `capabilities` is treated when it doesn't cover what the leaf
283    /// needs.
284    ///
285    /// If never called, explicitly set capabilities are used and unset
286    /// capabilities are derived from the leaf.
287    pub fn with_capabilities_policy(mut self, policy: CapabilitiesPolicy) -> Self {
288        self.capabilities_policy = Some(policy);
289        self
290    }
291
292    /// Build the [`LeafNodeParameters`].
293    pub fn build(self) -> LeafNodeParameters {
294        LeafNodeParameters {
295            credential_with_key: self.credential_with_key,
296            capabilities: self.capabilities,
297            extensions: self.extensions,
298            capabilities_policy: self.capabilities_policy,
299        }
300    }
301}
302
303/// This struct implements the MLS leaf node.
304///
305/// ```c
306/// // draft-ietf-mls-protocol-17
307/// struct {
308///     HPKEPublicKey encryption_key;
309///     SignaturePublicKey signature_key;
310///     Credential credential;
311///     Capabilities capabilities;
312///
313///     LeafNodeSource leaf_node_source;
314///     select (LeafNode.leaf_node_source) {
315///         case key_package:
316///             Lifetime lifetime;
317///
318///         case update:
319///             struct{};
320///
321///         case commit:
322///             opaque parent_hash<V>;
323///     };
324///
325///     Extension extensions<V>;
326///     /* SignWithLabel(., "LeafNodeTBS", LeafNodeTBS) */
327///     opaque signature<V>;
328/// } LeafNode;
329/// ```
330#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TlsSerialize, TlsSize)]
331pub struct LeafNode {
332    payload: LeafNodePayload,
333    signature: Signature,
334}
335
336/// Error building a [`LeafNode`]: either the leaf's capabilities don't
337/// cover what the leaf uses under [`CapabilitiesPolicy::Reject`],
338/// or an unrelated library error occurred (key generation, signing).
339#[derive(Error, Debug, PartialEq, Clone)]
340pub enum LeafNodeBuildError {
341    /// See [`LibraryError`] for more details.
342    #[error(transparent)]
343    LibraryError(#[from] LibraryError),
344    /// See [`LeafNodeValidationError`] for more details.
345    #[error(transparent)]
346    Validation(#[from] LeafNodeValidationError),
347}
348
349impl LeafNode {
350    /// Create a new [`LeafNode`].
351    /// This first creates a `LeadNodeTbs` and returns the result of signing
352    /// it.
353    ///
354    /// This function generates a fresh HPKE key pair for the leaf node and
355    /// returns the HPKE key pair along with the new leaf node.
356    /// The caller is responsible for storing the private key.
357    pub(crate) fn new(
358        provider: &impl OpenMlsProvider,
359        signer: &impl Signer,
360        new_leaf_node_params: NewLeafNodeParams,
361    ) -> Result<(Self, EncryptionKeyPair), LeafNodeBuildError> {
362        let NewLeafNodeParams {
363            ciphersuite,
364            credential_with_key,
365            leaf_node_source,
366            capabilities,
367            extensions,
368            tree_info_tbs,
369            constraints,
370            capabilities_policy,
371        } = new_leaf_node_params;
372
373        // Create a new encryption key pair.
374        let encryption_key_pair =
375            EncryptionKeyPair::random(provider.rand(), provider.crypto(), ciphersuite)?;
376
377        let leaf_node = Self::new_with_key(
378            ciphersuite,
379            encryption_key_pair.public_key().clone(),
380            credential_with_key,
381            leaf_node_source,
382            capabilities,
383            extensions,
384            tree_info_tbs,
385            &constraints,
386            capabilities_policy,
387            signer,
388        )?;
389
390        Ok((leaf_node, encryption_key_pair))
391    }
392
393    /// Create a new [`LeafNode`] from a caller-provided encryption key pair.
394    ///
395    /// Mirrors [`LeafNode::new`] but uses `encryption_key_pair` instead of
396    /// generating a fresh one. This is the virtual-clients KeyPackage build
397    /// hook: the encryption key is derived from the per-operation secret so a
398    /// sibling can reproduce it.
399    #[cfg(feature = "virtual-clients-draft")]
400    pub(crate) fn new_with_encryption_key_pair(
401        signer: &impl Signer,
402        new_leaf_node_params: NewLeafNodeParams,
403        encryption_key_pair: EncryptionKeyPair,
404    ) -> Result<(Self, EncryptionKeyPair), LeafNodeBuildError> {
405        let NewLeafNodeParams {
406            ciphersuite,
407            credential_with_key,
408            leaf_node_source,
409            capabilities,
410            extensions,
411            tree_info_tbs,
412            constraints,
413            capabilities_policy,
414        } = new_leaf_node_params;
415
416        let leaf_node = Self::new_with_key(
417            ciphersuite,
418            encryption_key_pair.public_key().clone(),
419            credential_with_key,
420            leaf_node_source,
421            capabilities,
422            extensions,
423            tree_info_tbs,
424            &constraints,
425            capabilities_policy,
426            signer,
427        )?;
428
429        Ok((leaf_node, encryption_key_pair))
430    }
431
432    /// Creates a new placeholder [`LeafNode`] that is used to build external
433    /// commits.
434    ///
435    /// Note: This is not a valid leaf node and it must be rekeyed and signed
436    /// before it can be used.
437    pub(crate) fn new_placeholder() -> Self {
438        let payload = LeafNodePayload {
439            encryption_key: EncryptionKey::from(Vec::new()),
440            signature_key: Vec::new().into(),
441            credential: Credential::new(CredentialType::Basic, Vec::new()),
442            capabilities: Capabilities::empty(),
443            leaf_node_source: LeafNodeSource::Update,
444            extensions: Extensions::default(),
445        };
446
447        Self {
448            payload,
449            signature: Vec::new().into(),
450        }
451    }
452
453    /// Create a new leaf node with a given HPKE encryption key pair.
454    /// The key pair must be stored in the key store by the caller.
455    #[allow(clippy::too_many_arguments)]
456    fn new_with_key(
457        ciphersuite: Ciphersuite,
458        encryption_key: EncryptionKey,
459        credential_with_key: CredentialWithKey,
460        leaf_node_source: LeafNodeSource,
461        capabilities: Capabilities,
462        extensions: Extensions<LeafNode>,
463        tree_info_tbs: TreeInfoTbs,
464        constraints: &LeafNodeConstraints,
465        capabilities_policy: CapabilitiesPolicy,
466        signer: &impl Signer,
467    ) -> Result<Self, LeafNodeBuildError> {
468        let leaf_node_tbs = LeafNodeTbs::new(
469            ciphersuite,
470            encryption_key,
471            credential_with_key,
472            capabilities,
473            leaf_node_source,
474            extensions,
475            tree_info_tbs,
476            constraints,
477            capabilities_policy,
478        )?;
479
480        leaf_node_tbs
481            .sign(signer)
482            .map_err(|_| LibraryError::custom("Signing failed").into())
483    }
484
485    /// New [`LeafNode`] with a parent hash.
486    ///
487    /// With the `virtual-clients-draft` feature, an
488    /// `encryption_key_pair_override` may be supplied. If `Some`, it is used
489    /// as the leaf's encryption keypair instead of generating a fresh one.
490    /// This is the hook for the virtual-clients-draft sender.
491    #[allow(clippy::too_many_arguments)]
492    pub(in crate::treesync) fn new_with_parent_hash(
493        rand: &impl OpenMlsRand,
494        crypto: &impl OpenMlsCrypto,
495        ciphersuite: Ciphersuite,
496        parent_hash: &[u8],
497        leaf_node_params: UpdateLeafNodeParams,
498        group_id: GroupId,
499        leaf_index: LeafNodeIndex,
500        signer: &impl Signer,
501        #[cfg(feature = "virtual-clients-draft")] encryption_key_pair_override: Option<
502            EncryptionKeyPair,
503        >,
504    ) -> Result<(Self, EncryptionKeyPair), LeafNodeBuildError> {
505        #[cfg(feature = "virtual-clients-draft")]
506        let encryption_key_pair = match encryption_key_pair_override {
507            Some(kp) => kp,
508            None => EncryptionKeyPair::random(rand, crypto, ciphersuite)?,
509        };
510        #[cfg(not(feature = "virtual-clients-draft"))]
511        let encryption_key_pair = EncryptionKeyPair::random(rand, crypto, ciphersuite)?;
512
513        let leaf_node_tbs = LeafNodeTbs::new(
514            ciphersuite,
515            encryption_key_pair.public_key().clone(),
516            leaf_node_params.credential_with_key,
517            leaf_node_params.capabilities,
518            LeafNodeSource::Commit(parent_hash.into()),
519            leaf_node_params.extensions,
520            TreeInfoTbs::Commit(TreePosition {
521                group_id,
522                leaf_index,
523            }),
524            &leaf_node_params.constraints,
525            leaf_node_params.capabilities_policy,
526        )?;
527
528        // Sign the leaf node
529        let leaf_node = leaf_node_tbs
530            .sign(signer)
531            .map_err(|_| LibraryError::custom("Signing failed"))?;
532
533        Ok((leaf_node, encryption_key_pair))
534    }
535
536    /// Generate a fresh leaf node.
537    ///
538    /// This includes generating a new encryption key pair that is stored in the
539    /// key store.
540    ///
541    /// This function can be used when generating an update. In most other cases
542    /// a leaf node should be generated as part of a new [`KeyPackage`].
543    #[cfg(all(test, feature = "generate-kats"))]
544    #[openmls_traits::maybe_async]
545    pub(crate) async fn generate_update<Provider: OpenMlsProvider>(
546        ciphersuite: Ciphersuite,
547        credential_with_key: CredentialWithKey,
548        capabilities: Capabilities,
549        extensions: Extensions<LeafNode>,
550        tree_info_tbs: TreeInfoTbs,
551        provider: &Provider,
552        signer: &impl Signer,
553    ) -> Result<Self, LeafNodeGenerationError<Provider::StorageError>> {
554        // Note that this function is supposed to be used in the public API only
555        // because it is interacting with the key store.
556
557        let new_leaf_node_params = NewLeafNodeParams {
558            ciphersuite,
559            credential_with_key,
560            leaf_node_source: LeafNodeSource::Update,
561            capabilities,
562            extensions,
563            tree_info_tbs,
564            // KAT generation only; there's no group to source constraints
565            // from, and the leaf built here is expected to already be
566            // self-consistent.
567            constraints: LeafNodeConstraints::default(),
568            capabilities_policy: CapabilitiesPolicy::Reject,
569        };
570
571        let (leaf_node, encryption_key_pair) = Self::new(provider, signer, new_leaf_node_params)?;
572
573        // Store the encryption key pair in the key store.
574        encryption_key_pair
575            .write(provider.storage())
576            .await
577            .map_err(LeafNodeGenerationError::StorageError)?;
578
579        Ok(leaf_node)
580    }
581
582    /// Update a leaf node.
583    ///
584    /// This function generates a new encryption key pair that is stored in the
585    /// key store and also returned.
586    ///
587    /// This function can be used when generating an update. In most other cases
588    /// a leaf node should be generated as part of a new [`KeyPackage`].
589    #[allow(clippy::too_many_arguments)]
590    #[openmls_traits::maybe_async]
591    pub(crate) async fn update<Provider: OpenMlsProvider>(
592        &mut self,
593        ciphersuite: Ciphersuite,
594        provider: &Provider,
595        signer: &impl Signer,
596        group_id: GroupId,
597        leaf_index: LeafNodeIndex,
598        leaf_node_parmeters: LeafNodeParameters,
599        constraints: &LeafNodeConstraints,
600    ) -> Result<EncryptionKeyPair, LeafNodeUpdateError<Provider::StorageError>> {
601        let tree_info = TreeInfoTbs::Update(TreePosition::new(group_id, leaf_index));
602        let mut leaf_node_tbs = LeafNodeTbs::from(self.clone(), tree_info);
603
604        // Update credential
605        if let Some(credential_with_key) = leaf_node_parmeters.credential_with_key {
606            leaf_node_tbs.payload.credential = credential_with_key.credential;
607            leaf_node_tbs.payload.signature_key = credential_with_key.signature_key;
608        }
609
610        // Update extensions
611        if let Some(extensions) = leaf_node_parmeters.extensions {
612            leaf_node_tbs.payload.extensions = extensions;
613        }
614
615        // Update capabilities
616        let (capabilities, capabilities_policy) = resolve_capabilities_for_existing_leaf(
617            leaf_node_parmeters.capabilities,
618            leaf_node_parmeters.capabilities_policy,
619            &leaf_node_tbs.payload.capabilities,
620        );
621        leaf_node_tbs.payload.capabilities = capabilities;
622
623        // Set the leaf node source to update
624        leaf_node_tbs.payload.leaf_node_source = LeafNodeSource::Update;
625
626        // `LeafNodeTbs::from` above bypasses `LeafNodeTbs::new`, the usual
627        // enforcement chokepoint, so it is applied here explicitly instead.
628        // This runs before the key pair below is generated and stored, so a
629        // rejected update leaves nothing behind in the key store.
630        leaf_node_tbs.payload.enforce_capabilities(
631            ciphersuite,
632            ProtocolVersion::default(),
633            constraints,
634            capabilities_policy,
635        )?;
636
637        // Create a new encryption key pair
638        let encryption_key_pair =
639            EncryptionKeyPair::random(provider.rand(), provider.crypto(), ciphersuite)?;
640        leaf_node_tbs.payload.encryption_key = encryption_key_pair.public_key().clone();
641
642        // Store the encryption key pair in the key store.
643        encryption_key_pair
644            .write(provider.storage())
645            .await
646            .map_err(LeafNodeUpdateError::Storage)?;
647
648        // Sign the leaf node
649        let leaf_node = leaf_node_tbs.sign(signer)?;
650        self.payload = leaf_node.payload;
651        self.signature = leaf_node.signature;
652
653        Ok(encryption_key_pair)
654    }
655
656    /// Returns the `encryption_key`.
657    pub fn encryption_key(&self) -> &EncryptionKey {
658        &self.payload.encryption_key
659    }
660
661    /// Returns the `signature_key` as byte slice.
662    pub fn signature_key(&self) -> &SignaturePublicKey {
663        &self.payload.signature_key
664    }
665
666    /// Returns the `credential`.
667    pub fn credential(&self) -> &Credential {
668        &self.payload.credential
669    }
670
671    /// Returns the `parent_hash` as byte slice or `None`.
672    pub fn parent_hash(&self) -> Option<&[u8]> {
673        match &self.payload.leaf_node_source {
674            LeafNodeSource::Commit(ph) => Some(ph.as_slice()),
675            _ => None,
676        }
677    }
678
679    /// Returns the [`Lifetime`] if present.
680    /// `None` otherwise.
681    pub(crate) fn life_time(&self) -> Option<&Lifetime> {
682        if let LeafNodeSource::KeyPackage(life_time) = &self.payload.leaf_node_source {
683            Some(life_time)
684        } else {
685            None
686        }
687    }
688
689    /// Returns a reference to the [`Signature`] of this leaf.
690    pub fn signature(&self) -> &Signature {
691        &self.signature
692    }
693
694    /// Return a reference to [`Capabilities`].
695    pub fn capabilities(&self) -> &Capabilities {
696        &self.payload.capabilities
697    }
698
699    /// Return a reference to the leaf node source.
700    pub fn leaf_node_source(&self) -> &LeafNodeSource {
701        &self.payload.leaf_node_source
702    }
703
704    /// Return a reference to the leaf node extensions.
705    pub fn extensions(&self) -> &Extensions<LeafNode> {
706        &self.payload.extensions
707    }
708
709    /// The virtual-client derivation info this leaf might contain.
710    #[cfg(feature = "virtual-clients-draft")]
711    pub(crate) fn vc_derivation_info(
712        &self,
713    ) -> Result<
714        Option<crate::components::vc_derivation_info::DerivationInfo>,
715        crate::components::vc_derivation_info::VirtualClientsError,
716    > {
717        use tls_codec::DeserializeBytes as _;
718
719        use crate::components::vc_derivation_info::{
720            DerivationInfo, VirtualClientsError, VC_COMPONENT_ID,
721        };
722
723        let Some(bytes) = self
724            .extensions()
725            .app_data_dictionary()
726            .and_then(|dict| dict.dictionary().get(&VC_COMPONENT_ID))
727        else {
728            return Ok(None);
729        };
730        DerivationInfo::tls_deserialize_exact_bytes(bytes)
731            .map(Some)
732            .map_err(|e| {
733                log::error!("vc: leaf derivation info deserialize failed: {e:?}");
734                VirtualClientsError::DerivationInfoMalformed
735            })
736    }
737
738    /// Returns `true` if the [`ExtensionType`] is supported by this leaf node.
739    pub(crate) fn supports_extension(&self, extension_type: &ExtensionType) -> bool {
740        extension_type.is_default()
741            || self
742                .payload
743                .capabilities
744                .extensions
745                .contains(extension_type)
746    }
747
748    /// Check whether the this leaf node supports all the required extensions
749    /// in the provided list.
750    pub(crate) fn check_extension_support(
751        &self,
752        extensions: &[ExtensionType],
753    ) -> Result<(), LeafNodeValidationError> {
754        let mut required = extensions.iter().filter(|e| !e.is_default()).peekable();
755
756        // Skip building the lookup if there are no non-default extensions.
757        if required.peek().is_none() {
758            return Ok(());
759        }
760
761        let supported: HashSet<ExtensionType> = self
762            .payload
763            .capabilities
764            .extensions
765            .iter()
766            .copied()
767            .collect();
768
769        if let Some(unsupported) = required.find(|e| !supported.contains(e)) {
770            log::error!(
771                "Leaf node does not support required extension {:?}\n
772                    Supported extensions: {:?}",
773                unsupported,
774                self.payload.capabilities.extensions
775            );
776            return Err(LeafNodeValidationError::UnsupportedExtensions);
777        }
778
779        Ok(())
780    }
781
782    /// Perform all checks that can be done without further context:
783    /// - the used extensions are not known to be invalid in leaf nodes
784    /// - the types of the used extensions are covered by the capabilities
785    /// - the type of the credential is covered by the capabilities
786    pub(crate) fn validate_locally(&self) -> Result<(), LeafNodeValidationError> {
787        self.payload.validate_locally()
788    }
789}
790
791/// The payload of a [`LeafNode`]
792///
793/// ```text
794/// struct {
795///     HPKEPublicKey encryption_key;
796///     SignaturePublicKey signature_key;
797///     Credential credential;
798///     Capabilities capabilities;
799///
800///     LeafNodeSource leaf_node_source;
801///     select (LeafNode.leaf_node_source) {
802///         case key_package:
803///             Lifetime lifetime;
804///
805///         case update:
806///             struct{};
807///
808///         case commit:
809///             opaque parent_hash<V>;
810///     };
811///
812///     Extension extensions<V>;
813///     ...
814/// } LeafNode;
815/// ```
816#[derive(
817    Debug,
818    Clone,
819    PartialEq,
820    Eq,
821    Serialize,
822    Deserialize,
823    TlsSerialize,
824    TlsDeserialize,
825    TlsDeserializeBytes,
826    TlsSize,
827)]
828struct LeafNodePayload {
829    encryption_key: EncryptionKey,
830    signature_key: SignaturePublicKey,
831    credential: Credential,
832    capabilities: Capabilities,
833    leaf_node_source: LeafNodeSource,
834    extensions: Extensions<LeafNode>,
835}
836
837impl LeafNodePayload {
838    /// Perform all checks that can be done without further context:
839    /// - the used extensions are not known to be invalid in leaf nodes
840    /// - the types of the used extensions are covered by the capabilities
841    /// - the type of the credential is covered by the capabilities
842    ///
843    /// This lives on the payload rather than on [`LeafNode`] so that leaf
844    /// construction can run it *before* signing, and so that a leaf we build
845    /// is held to exactly the checks a peer will apply on receipt.
846    pub(crate) fn validate_locally(&self) -> Result<(), LeafNodeValidationError> {
847        // Check that no extension is invalid when used in leaf nodes.
848        // https://validation.openmls.tech/#valn1601
849        // NOTE: This check is conducted manually for now, instead of using the method
850        // Extensions::validate_extension_types_for_leaf_node(),
851        // in order to collect the invalid extension types for the log message below.
852        // However, it could be better to instead return the list of invalid extension types
853        // as part of Extensions::validate_extension_types_for_leaf_node(),
854        // as part of the error message.
855        let invalid_extension_types = self
856            .extensions
857            .iter()
858            .filter(|ext| !ext.extension_type().is_valid_in_leaf_node())
859            .collect::<Vec<_>>();
860        if !invalid_extension_types.is_empty() {
861            log::error!("Invalid extension used in leaf node: {invalid_extension_types:?}");
862            return Err(LeafNodeValidationError::UnsupportedExtensions);
863        }
864
865        // Check that all extensions are contained in the capabilities.
866        if !self.capabilities.contains_extensions(&self.extensions) {
867            log::error!(
868                "Leaf node does not support all extensions it uses\n
869                Supported extensions: {:?}\n
870                Used extensions: {:?}",
871                self.capabilities.extensions(),
872                self.extensions
873            );
874            return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
875        }
876
877        // Check that the capabilities contain the leaf node's credential type.
878        // (https://validation.openmls.tech/#valn0113)
879        if !self
880            .capabilities
881            .contains_credential(self.credential.credential_type())
882        {
883            return Err(LeafNodeValidationError::CredentialNotInCapabilities);
884        }
885
886        Ok(())
887    }
888
889    /// Apply `policy` to this leaf's capabilities, then check them.
890    ///
891    /// This is the single enforcement point for every leaf OpenMLS builds. It
892    /// is reached from [`LeafNodeTbs::new`] (every freshly-built leaf) and from
893    /// [`LeafNode::update`], which mutates an existing signed leaf in place and
894    /// so bypasses `LeafNodeTbs::new`.
895    ///
896    /// The checks run under both policies, so widening is verified as well.
897    /// Together with `constraints` they are the capability checks a
898    /// peer applies on receipt ([`LeafNodePayload::validate_locally`]) and
899    /// [`LeafNodeConstraints`]. Widening never covers `constraints`.
900    fn enforce_capabilities(
901        &mut self,
902        ciphersuite: Ciphersuite,
903        version: ProtocolVersion,
904        constraints: &LeafNodeConstraints,
905        policy: CapabilitiesPolicy,
906    ) -> Result<(), LeafNodeValidationError> {
907        if matches!(policy, CapabilitiesPolicy::Widen) {
908            self.capabilities.widen_for(
909                ciphersuite,
910                self.credential.credential_type(),
911                &self.extensions,
912            );
913        }
914        self.capabilities.ensure_version(version);
915
916        self.validate_locally()?;
917
918        if !self
919            .capabilities
920            .contains_ciphersuite(VerifiableCiphersuite::from(ciphersuite))
921        {
922            return Err(LeafNodeValidationError::CiphersuiteNotInCapabilities);
923        }
924
925        constraints.check(&self.capabilities, self.credential.credential_type())?;
926
927        Ok(())
928    }
929}
930
931/// The source of the `LeafNode`.
932#[derive(
933    Debug,
934    Clone,
935    PartialEq,
936    Eq,
937    Serialize,
938    Deserialize,
939    TlsSerialize,
940    TlsDeserialize,
941    TlsDeserializeBytes,
942    TlsSize,
943)]
944#[repr(u8)]
945pub enum LeafNodeSource {
946    /// The leaf node was added to the group as part of a key package.
947    #[tls_codec(discriminant = 1)]
948    KeyPackage(Lifetime),
949    /// The leaf node was added through an Update proposal.
950    Update,
951    /// The leaf node was added via a Commit.
952    Commit(ParentHash),
953}
954
955pub type ParentHash = VLBytes;
956
957/// To-be-signed leaf node.
958///
959/// ```c
960/// // draft-ietf-mls-protocol-17
961/// struct {
962///     HPKEPublicKey encryption_key;
963///     SignaturePublicKey signature_key;
964///     Credential credential;
965///     Capabilities capabilities;
966///
967///     LeafNodeSource leaf_node_source;
968///     select (LeafNodeTBS.leaf_node_source) {
969///         case key_package:
970///             Lifetime lifetime;
971///
972///         case update:
973///             struct{};
974///
975///         case commit:
976///             opaque parent_hash<V>;
977///     };
978///
979///     Extension extensions<V>;
980///
981///     // ... continued in [`TreeInfo`] ...
982/// } LeafNodeTBS;
983/// ```
984#[derive(Debug, TlsSerialize, TlsSize)]
985pub struct LeafNodeTbs {
986    payload: LeafNodePayload,
987    tree_info_tbs: TreeInfoTbs,
988}
989
990impl LeafNodeTbs {
991    /// Build a [`LeafNodeTbs`] from a [`LeafNode`] and a [`TreeInfo`]
992    /// to update a leaf node.
993    pub(crate) fn from(leaf_node: LeafNode, tree_info_tbs: TreeInfoTbs) -> Self {
994        Self {
995            payload: leaf_node.payload,
996            tree_info_tbs,
997        }
998    }
999
1000    /// Build a new [`LeafNodeTbs`] from a [`KeyPackage`] and [`Credential`].
1001    /// To get the [`LeafNode`] call [`LeafNode::sign`].
1002    ///
1003    /// This is the single point through which every leaf node the library
1004    /// creates is built, so it is where we enforce that a leaf's capabilities
1005    /// are consistent with the leaf itself and the group's requirements — see
1006    /// [`LeafNodePayload::enforce_capabilities`].
1007    #[allow(clippy::too_many_arguments)]
1008    pub(crate) fn new(
1009        ciphersuite: Ciphersuite,
1010        encryption_key: EncryptionKey,
1011        credential_with_key: CredentialWithKey,
1012        capabilities: Capabilities,
1013        leaf_node_source: LeafNodeSource,
1014        extensions: Extensions<LeafNode>,
1015        tree_info_tbs: TreeInfoTbs,
1016        constraints: &LeafNodeConstraints,
1017        capabilities_policy: CapabilitiesPolicy,
1018    ) -> Result<Self, LeafNodeValidationError> {
1019        let mut payload = LeafNodePayload {
1020            encryption_key,
1021            signature_key: credential_with_key.signature_key,
1022            credential: credential_with_key.credential,
1023            capabilities,
1024            leaf_node_source,
1025            extensions,
1026        };
1027
1028        payload.enforce_capabilities(
1029            ciphersuite,
1030            ProtocolVersion::default(),
1031            constraints,
1032            capabilities_policy,
1033        )?;
1034
1035        Ok(LeafNodeTbs {
1036            payload,
1037            tree_info_tbs,
1038        })
1039    }
1040}
1041
1042/// Helper struct that holds additional information required to sign a leaf node.
1043///
1044/// ```c
1045/// // draft-ietf-mls-protocol-17
1046/// struct {
1047///     // ... continued from [`LeafNodeTbs`] ...
1048///
1049///     select (LeafNodeTBS.leaf_node_source) {
1050///         case key_package:
1051///             struct{};
1052///
1053///         case update:
1054///             opaque group_id<V>;
1055///             uint32 leaf_index;
1056///
1057///         case commit:
1058///             opaque group_id<V>;
1059///             uint32 leaf_index;
1060///     };
1061/// } LeafNodeTBS;
1062/// ```
1063#[derive(Debug)]
1064pub(crate) enum TreeInfoTbs {
1065    KeyPackage,
1066    Update(TreePosition),
1067    Commit(TreePosition),
1068}
1069
1070#[derive(Debug, Clone, PartialEq, Eq, TlsSerialize, TlsSize)]
1071pub(crate) struct TreePosition {
1072    group_id: GroupId,
1073    leaf_index: LeafNodeIndex,
1074}
1075
1076impl TreePosition {
1077    pub(crate) fn new(group_id: GroupId, leaf_index: LeafNodeIndex) -> Self {
1078        Self {
1079            group_id,
1080            leaf_index,
1081        }
1082    }
1083
1084    #[cfg(feature = "test-utils")]
1085    pub(crate) fn into_parts(self) -> (GroupId, LeafNodeIndex) {
1086        (self.group_id, self.leaf_index)
1087    }
1088}
1089
1090const LEAF_NODE_SIGNATURE_LABEL: &str = "LeafNodeTBS";
1091
1092#[derive(
1093    Debug,
1094    Clone,
1095    PartialEq,
1096    Eq,
1097    Serialize,
1098    Deserialize,
1099    TlsSerialize,
1100    TlsDeserialize,
1101    TlsDeserializeBytes,
1102    TlsSize,
1103)]
1104pub struct LeafNodeIn {
1105    payload: LeafNodePayload,
1106    signature: Signature,
1107}
1108
1109impl LeafNodeIn {
1110    pub(crate) fn into_verifiable_leaf_node(self) -> VerifiableLeafNode {
1111        match self.payload.leaf_node_source {
1112            LeafNodeSource::KeyPackage(_) => {
1113                let verifiable = VerifiableKeyPackageLeafNode {
1114                    payload: self.payload,
1115                    signature: self.signature,
1116                };
1117                VerifiableLeafNode::KeyPackage(verifiable)
1118            }
1119            LeafNodeSource::Update => {
1120                let verifiable = VerifiableUpdateLeafNode {
1121                    payload: self.payload,
1122                    signature: self.signature,
1123                    tree_position: None,
1124                };
1125                VerifiableLeafNode::Update(verifiable)
1126            }
1127            LeafNodeSource::Commit(_) => {
1128                let verifiable = VerifiableCommitLeafNode {
1129                    payload: self.payload,
1130                    signature: self.signature,
1131                    tree_position: None,
1132                };
1133                VerifiableLeafNode::Commit(verifiable)
1134            }
1135        }
1136    }
1137
1138    /// Returns the `encryption_key` as byte slice.
1139    pub fn encryption_key(&self) -> &EncryptionKey {
1140        &self.payload.encryption_key
1141    }
1142
1143    /// Returns the `signature_key` as byte slice.
1144    pub fn signature_key(&self) -> &SignaturePublicKey {
1145        &self.payload.signature_key
1146    }
1147
1148    /// Returns the `signature_key` as byte slice.
1149    pub fn credential(&self) -> &Credential {
1150        &self.payload.credential
1151    }
1152
1153    /// Assume that signature is valid and return the corresponding [`LeafNode`].
1154    ///
1155    /// # Safety
1156    ///
1157    /// The caller must guarantee that the leaf node is verified.
1158    #[cfg(feature = "unchecked-conversions")]
1159    pub fn into_unchecked(self) -> LeafNode {
1160        LeafNode {
1161            payload: self.payload,
1162            signature: self.signature,
1163        }
1164    }
1165}
1166
1167impl From<LeafNode> for LeafNodeIn {
1168    fn from(leaf_node: LeafNode) -> Self {
1169        Self {
1170            payload: leaf_node.payload,
1171            signature: leaf_node.signature,
1172        }
1173    }
1174}
1175
1176#[cfg(any(feature = "test-utils", test))]
1177impl From<LeafNodeIn> for LeafNode {
1178    fn from(deserialized: LeafNodeIn) -> Self {
1179        Self {
1180            payload: deserialized.payload,
1181            signature: deserialized.signature,
1182        }
1183    }
1184}
1185
1186impl From<KeyPackage> for LeafNode {
1187    fn from(key_package: KeyPackage) -> Self {
1188        key_package.leaf_node().clone()
1189    }
1190}
1191
1192impl From<KeyPackageBundle> for LeafNode {
1193    fn from(key_package: KeyPackageBundle) -> Self {
1194        key_package.key_package().leaf_node().clone()
1195    }
1196}
1197
1198#[derive(Debug, Clone, PartialEq, Eq)]
1199pub(crate) enum VerifiableLeafNode {
1200    KeyPackage(VerifiableKeyPackageLeafNode),
1201    Update(VerifiableUpdateLeafNode),
1202    Commit(VerifiableCommitLeafNode),
1203}
1204
1205impl VerifiableLeafNode {
1206    pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
1207        match self {
1208            VerifiableLeafNode::KeyPackage(v) => v.signature_key(),
1209            VerifiableLeafNode::Update(v) => v.signature_key(),
1210            VerifiableLeafNode::Commit(v) => v.signature_key(),
1211        }
1212    }
1213}
1214
1215#[derive(Debug, Clone, PartialEq, Eq)]
1216pub(crate) struct VerifiableKeyPackageLeafNode {
1217    payload: LeafNodePayload,
1218    signature: Signature,
1219}
1220
1221impl VerifiableKeyPackageLeafNode {
1222    pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
1223        &self.payload.signature_key
1224    }
1225}
1226
1227// https://validation.openmls.tech/#valn0102
1228impl Verifiable for VerifiableKeyPackageLeafNode {
1229    type VerifiedStruct = LeafNode;
1230
1231    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
1232        self.payload.tls_serialize_detached()
1233    }
1234
1235    fn signature(&self) -> &Signature {
1236        &self.signature
1237    }
1238
1239    fn label(&self) -> &str {
1240        LEAF_NODE_SIGNATURE_LABEL
1241    }
1242
1243    fn verify(
1244        self,
1245        crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
1246        pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
1247    ) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
1248        self.verify_no_out(crypto, pk)?;
1249        Ok(LeafNode {
1250            payload: self.payload,
1251            signature: self.signature,
1252        })
1253    }
1254}
1255
1256impl VerifiedStruct for LeafNode {}
1257
1258#[derive(Debug, Clone, PartialEq, Eq)]
1259pub(crate) struct VerifiableUpdateLeafNode {
1260    payload: LeafNodePayload,
1261    signature: Signature,
1262    tree_position: Option<TreePosition>,
1263}
1264
1265impl VerifiableUpdateLeafNode {
1266    pub(crate) fn add_tree_position(&mut self, tree_info: TreePosition) {
1267        self.tree_position = Some(tree_info);
1268    }
1269
1270    pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
1271        &self.payload.signature_key
1272    }
1273}
1274
1275impl Verifiable for VerifiableUpdateLeafNode {
1276    type VerifiedStruct = LeafNode;
1277
1278    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
1279        let tree_info_tbs = match &self.tree_position {
1280            Some(tree_position) => TreeInfoTbs::Commit(tree_position.clone()),
1281            None => return Err(tls_codec::Error::InvalidInput),
1282        };
1283        let leaf_node_tbs = LeafNodeTbs {
1284            payload: self.payload.clone(),
1285            tree_info_tbs,
1286        };
1287        leaf_node_tbs.tls_serialize_detached()
1288    }
1289
1290    fn signature(&self) -> &Signature {
1291        &self.signature
1292    }
1293
1294    fn label(&self) -> &str {
1295        LEAF_NODE_SIGNATURE_LABEL
1296    }
1297
1298    fn verify(
1299        self,
1300        crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
1301        pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
1302    ) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
1303        self.verify_no_out(crypto, pk)?;
1304        Ok(LeafNode {
1305            payload: self.payload,
1306            signature: self.signature,
1307        })
1308    }
1309}
1310
1311#[derive(Debug, Clone, PartialEq, Eq)]
1312pub(crate) struct VerifiableCommitLeafNode {
1313    payload: LeafNodePayload,
1314    signature: Signature,
1315    tree_position: Option<TreePosition>,
1316}
1317
1318impl VerifiableCommitLeafNode {
1319    pub(crate) fn add_tree_position(&mut self, tree_info: TreePosition) {
1320        self.tree_position = Some(tree_info);
1321    }
1322
1323    pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
1324        &self.payload.signature_key
1325    }
1326}
1327
1328impl Verifiable for VerifiableCommitLeafNode {
1329    type VerifiedStruct = LeafNode;
1330
1331    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
1332        let tree_info_tbs = match &self.tree_position {
1333            Some(tree_position) => TreeInfoTbs::Commit(tree_position.clone()),
1334            None => return Err(tls_codec::Error::InvalidInput),
1335        };
1336        let leaf_node_tbs = LeafNodeTbs {
1337            payload: self.payload.clone(),
1338            tree_info_tbs,
1339        };
1340
1341        leaf_node_tbs.tls_serialize_detached()
1342    }
1343
1344    fn signature(&self) -> &Signature {
1345        &self.signature
1346    }
1347
1348    fn label(&self) -> &str {
1349        LEAF_NODE_SIGNATURE_LABEL
1350    }
1351
1352    fn verify(
1353        self,
1354        crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
1355        pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
1356    ) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
1357        self.verify_no_out(crypto, pk)?;
1358        Ok(LeafNode {
1359            payload: self.payload,
1360            signature: self.signature,
1361        })
1362    }
1363}
1364
1365impl Signable for LeafNodeTbs {
1366    type SignedOutput = LeafNode;
1367
1368    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
1369        self.tls_serialize_detached()
1370    }
1371
1372    fn label(&self) -> &str {
1373        LEAF_NODE_SIGNATURE_LABEL
1374    }
1375}
1376
1377impl SignedStruct<LeafNodeTbs> for LeafNode {
1378    fn from_payload(tbs: LeafNodeTbs, signature: Signature, _serialized_payload: Vec<u8>) -> Self {
1379        Self {
1380            payload: tbs.payload,
1381            signature,
1382        }
1383    }
1384}
1385
1386#[cfg(all(test, feature = "generate-kats"))]
1387#[derive(Error, Debug, PartialEq, Clone)]
1388pub enum LeafNodeGenerationError<StorageError> {
1389    /// See [`LibraryError`] for more details.
1390    #[error(transparent)]
1391    LibraryError(#[from] LibraryError),
1392
1393    /// See [`LeafNodeBuildError`] for more details.
1394    #[error(transparent)]
1395    Build(#[from] LeafNodeBuildError),
1396
1397    /// Error storing leaf private key in storage.
1398    #[error("Error storing leaf private key.")]
1399    StorageError(StorageError),
1400}
1401
1402/// Leaf Node Update Error
1403#[derive(Error, Debug, PartialEq, Clone)]
1404pub enum LeafNodeUpdateError<StorageError> {
1405    /// See [`LibraryError`] for more details.
1406    #[error(transparent)]
1407    LibraryError(#[from] LibraryError),
1408
1409    /// Error storing leaf private key in storage.
1410    #[error("Error storing leaf private key.")]
1411    Storage(StorageError),
1412
1413    /// Signature error.
1414    #[error(transparent)]
1415    Signature(#[from] crate::ciphersuite::signable::SignatureError),
1416
1417    /// See [`LeafNodeValidationError`] for more details.
1418    #[error(transparent)]
1419    Validation(#[from] LeafNodeValidationError),
1420}