Skip to main content

openmls/key_packages/
mod.rs

1//! # Key Packages
2//!
3//! Key packages are pre-published public keys that carry information about a
4//! user, allowing for the asynchronous addition of clients to an MLS group.
5//!
6//! A key package object specifies:
7//!
8//! - A **protocol version** and ciphersuite that the client supports
9//! - A **public key** that others can use for key agreement
10//! - A **credential** authenticating the client's application-layer identity
11//! - A list of **extensions** for the key package (see
12//!   [Extensions](`mod@crate::extensions`) for details)
13//!
14//! Key packages are meant to be used only once and SHOULD NOT be reused,
15//! except as a last resort—i.e., when no other key package is available.
16//! Clients MAY generate and publish multiple key packages to support multiple
17//! ciphersuites.
18//!
19//! The HPKE init key MUST be a public key for the asymmetric encryption scheme
20//! defined by the ciphersuite. It MUST be unique among key packages created by
21//! the client. The entire structure is signed using the client's signature key.
22//! A key package object with an invalid signature field is considered malformed.
23//!
24//! ## Creating key package bundles
25//!
26//! Key package bundles are key packages that include their private key. A key
27//! package bundle can be created as follows:
28//!
29//! ```
30//! use openmls::{prelude::{*, tls_codec::*}};
31//! use openmls_rust_crypto::OpenMlsRustCrypto;
32//! use openmls_basic_credential::SignatureKeyPair;
33//!
34//! let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
35//! let provider = OpenMlsRustCrypto::default();
36//!
37//! let credential = BasicCredential::new("identity".into());
38//! let signer =
39//!     SignatureKeyPair::new(ciphersuite.signature_algorithm())
40//!         .expect("Error generating a signature key pair.");
41//! let credential_with_key = CredentialWithKey {
42//!     credential: credential.into(),
43//!     signature_key: signer.public().into(),
44//! };
45//! let key_package = KeyPackage::builder()
46//!     .build(
47//!         ciphersuite,
48//!         &provider,
49//!         &signer,
50//!         credential_with_key,
51//!     )
52//!     .unwrap();
53//! ```
54//!
55//! See [`KeyPackage`] for more details and other ways to create key packages.
56//!
57//! ## Loading key packages
58//!
59//! When getting key packages from another user the serialized bytes are parsed
60//! as follows;
61//!
62//! ```
63//! use openmls::prelude::{*, tls_codec::*};
64//! use openmls::test_utils::hex_to_bytes;
65//! use openmls_rust_crypto::OpenMlsRustCrypto;
66//!
67//! let provider = OpenMlsRustCrypto::default();
68//! let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
69//!
70//! let key_package_bytes = hex_to_bytes(
71//!         "0001000120D4F26FCA6EF6B1CA2FDD8DCAA501730FB003323AD8C781490B94782771\
72//!         B22216208E9BF17CE632EC753A9BFC624F275AA745ACD7316A5CF18B8E39CE71A80EE\
73//!         137205639176E415B378BA54B9E7C678FFAA860676CEFEDFA0DD3FF692F20AC7E2632\
74//!         0001086964656E74697479020001060001000200030200010C0001000200030004000\
75//!         50007020001010000000064A1986E00000000776DA97E004040A71F1B7A5F78D15C3F\
76//!         B215D811BADB0BBBD78B582D42E5C3672085699DCA5F90DAA57BB74A3A973789E7006\
77//!         887FCE85F0E64C19C1F26C28B5752B3C3312FF3040040407B5A96167512061E78414F\
78//!         E3F29B89FF2A954CB8E0A6E976EA039E0A1A0AB91B80664BDDC62BC8CBE64BC9242C4\
79//!         CDC33F56A10E425A384AED029C23E1D467C0E");
80//!
81//! let key_package_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes.as_slice())
82//!     .expect("Could not deserialize KeyPackage");
83//!
84//! let key_package = key_package_in
85//!     .validate(provider.crypto(), ProtocolVersion::Mls10)
86//!     .expect("Invalid KeyPackage");
87//! ```
88//!
89//! See [`KeyPackage`] for more details on how to use key packages.
90
91#[cfg(not(feature = "extensions-draft"))]
92use crate::extensions::LastResortExtension;
93use crate::{
94    ciphersuite::{
95        hash_ref::{make_key_package_ref, KeyPackageRef},
96        signable::*,
97        *,
98    },
99    credentials::*,
100    error::LibraryError,
101    extensions::{Extension, ExtensionType, Extensions},
102    storage::OpenMlsProvider,
103    treesync::{
104        node::{
105            encryption_keys::{EncryptionKeyPair, EncryptionPrivateKey},
106            leaf_node::{
107                resolve_capabilities, Capabilities, CapabilitiesPolicy, LeafNodeConstraints,
108                LeafNodeSource, NewLeafNodeParams, TreeInfoTbs,
109            },
110        },
111        LeafNode,
112    },
113    versions::ProtocolVersion,
114};
115#[cfg(feature = "extensions-draft")]
116use crate::{
117    component::{ComponentId, ComponentType},
118    extensions::AppDataDictionaryExtension,
119};
120use openmls_traits::{
121    crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider, types::Ciphersuite,
122};
123use serde::{Deserialize, Serialize};
124use tls_codec::{
125    Serialize as TlsSerializeTrait, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
126};
127
128// Private
129use errors::*;
130
131// Public
132pub mod errors;
133pub mod key_package_in;
134
135mod lifetime;
136#[cfg(feature = "virtual-clients-draft")]
137mod vc;
138
139// Tests
140#[cfg(test)]
141pub(crate) mod tests;
142
143// Public types
144pub use key_package_in::KeyPackageIn;
145pub use lifetime::Lifetime;
146#[cfg(feature = "virtual-clients-draft")]
147pub use vc::{VcKeyPackageBatch, VcKeyPackageBatchBuilder};
148
149/// The unsigned payload of a key package.
150/// Any modification must happen on this unsigned struct. Use `sign` to get a
151/// signed key package.
152///
153/// ```text
154/// struct {
155///     ProtocolVersion version;
156///     CipherSuite cipher_suite;
157///     HPKEPublicKey init_key;
158///     LeafNode leaf_node;
159///     Extension extensions<V>;
160/// } KeyPackageTBS;
161/// ```
162#[derive(Debug, Clone, PartialEq, TlsSize, TlsSerialize, Serialize, Deserialize)]
163struct KeyPackageTbs {
164    protocol_version: ProtocolVersion,
165    ciphersuite: Ciphersuite,
166    init_key: InitKey,
167    leaf_node: LeafNode,
168    extensions: Extensions<KeyPackage>,
169}
170
171impl Signable for KeyPackageTbs {
172    type SignedOutput = KeyPackage;
173
174    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
175        self.tls_serialize_detached()
176    }
177
178    fn label(&self) -> &str {
179        SIGNATURE_KEY_PACKAGE_LABEL
180    }
181}
182
183impl From<KeyPackage> for KeyPackageTbs {
184    fn from(kp: KeyPackage) -> Self {
185        kp.payload
186    }
187}
188
189/// The key package struct.
190#[derive(Debug, Clone, Serialize, Deserialize, TlsSize)]
191pub struct KeyPackage {
192    payload: KeyPackageTbs,
193    signature: Signature,
194    #[serde(skip)]
195    #[tls_codec(skip)]
196    serialized_payload: Option<Vec<u8>>,
197}
198
199impl TlsSerializeTrait for KeyPackage {
200    fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
201        let mut written = 0;
202        if let Some(ref bytes) = self.serialized_payload {
203            writer.write_all(bytes)?;
204            written += bytes.len();
205        } else {
206            written += self.payload.tls_serialize(writer)?;
207        }
208        written += self.signature.tls_serialize(writer)?;
209        Ok(written)
210    }
211}
212
213impl PartialEq for KeyPackage {
214    fn eq(&self, other: &Self) -> bool {
215        // We ignore the signature in the comparison. The same key package
216        // may have different, valid signatures.
217        self.payload == other.payload
218    }
219}
220
221impl SignedStruct<KeyPackageTbs> for KeyPackage {
222    fn from_payload(
223        payload: KeyPackageTbs,
224        signature: Signature,
225        serialized_payload: Vec<u8>,
226    ) -> Self {
227        Self {
228            payload,
229            signature,
230            serialized_payload: Some(serialized_payload),
231        }
232    }
233}
234
235const SIGNATURE_KEY_PACKAGE_LABEL: &str = "KeyPackageTBS";
236
237#[cfg(feature = "extensions-draft")]
238fn last_resort_component_id() -> ComponentId {
239    ComponentType::LastResortKeyPackage.into()
240}
241
242/// Check whether KeyPackage extensions carry a last-resort marker. See
243/// [`KeyPackage::last_resort`].
244pub(crate) fn has_last_resort_marker(extensions: &Extensions<KeyPackage>) -> bool {
245    #[cfg(feature = "extensions-draft")]
246    if matches!(
247        extensions
248            .app_data_dictionary()
249            .and_then(|extension| extension.dictionary().get(&last_resort_component_id())),
250        Some(data) if data.is_empty()
251    ) {
252        return true;
253    }
254
255    extensions.contains(ExtensionType::LastResort)
256}
257
258/// The leaf-node-specific parameters used when creating a [`KeyPackage`].
259pub(crate) struct KeyPackageLeafNodeParams {
260    pub(crate) lifetime: Lifetime,
261    pub(crate) capabilities: Capabilities,
262    pub(crate) extensions: Extensions<LeafNode>,
263    /// How `capabilities` is treated when it doesn't cover what the leaf
264    /// needs. A bare `KeyPackage` has no group context, so there are no
265    /// required capabilities to check against.
266    pub(crate) capabilities_policy: CapabilitiesPolicy,
267}
268
269/// Helper struct containing the results of building a new [`KeyPackage`].
270pub(crate) struct KeyPackageCreationResult {
271    pub key_package: KeyPackage,
272    pub encryption_keypair: EncryptionKeyPair,
273    pub init_private_key: HpkePrivateKey,
274}
275
276/// Init key for HPKE.
277#[derive(
278    Debug,
279    Clone,
280    PartialEq,
281    TlsSize,
282    TlsSerialize,
283    Serialize,
284    Deserialize,
285    TlsDeserialize,
286    TlsDeserializeBytes,
287)]
288pub struct InitKey {
289    key: HpkePublicKey,
290}
291
292impl InitKey {
293    /// Return the internal [`HpkePublicKey`].
294    pub fn key(&self) -> &HpkePublicKey {
295        &self.key
296    }
297
298    /// Return the internal [`HpkePublicKey`] as a slice.
299    pub fn as_slice(&self) -> &[u8] {
300        self.key.as_slice()
301    }
302}
303
304impl From<Vec<u8>> for InitKey {
305    fn from(key: Vec<u8>) -> Self {
306        Self {
307            key: HpkePublicKey::from(key),
308        }
309    }
310}
311
312impl From<HpkePublicKey> for InitKey {
313    fn from(key: HpkePublicKey) -> Self {
314        Self { key }
315    }
316}
317
318// Public `KeyPackage` functions.
319impl KeyPackage {
320    /// Create a key package builder.
321    ///
322    /// This is provided for convenience. You can also use [`KeyPackageBuilder::new`].
323    pub fn builder() -> KeyPackageBuilder {
324        KeyPackageBuilder::new()
325    }
326
327    /// Create a new key package for the given `ciphersuite` and `identity`.
328    pub(crate) fn create(
329        ciphersuite: Ciphersuite,
330        provider: &impl OpenMlsProvider,
331        signer: &impl Signer,
332        credential_with_key: CredentialWithKey,
333        extensions: Extensions<KeyPackage>,
334        leaf_node_params: KeyPackageLeafNodeParams,
335    ) -> Result<KeyPackageCreationResult, KeyPackageNewError> {
336        if ciphersuite.signature_algorithm() != signer.signature_scheme() {
337            return Err(KeyPackageNewError::CiphersuiteSignatureSchemeMismatch);
338        }
339
340        provider
341            .crypto()
342            .supports(ciphersuite)
343            .map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
344
345        // Create a new HPKE key pair
346        let ikm = Secret::random(ciphersuite, provider.rand())
347            .map_err(LibraryError::unexpected_crypto_error)?;
348        let init_key = provider
349            .crypto()
350            .derive_hpke_keypair(ciphersuite.hpke_config(), ikm.as_slice())
351            .map_err(|e| {
352                KeyPackageNewError::LibraryError(LibraryError::unexpected_crypto_error(e))
353            })?;
354        let (key_package, encryption_keypair) = Self::new_from_keys(
355            ciphersuite,
356            provider,
357            signer,
358            credential_with_key,
359            extensions,
360            leaf_node_params,
361            init_key.public.into(),
362        )?;
363
364        Ok(KeyPackageCreationResult {
365            key_package,
366            encryption_keypair,
367            init_private_key: init_key.private,
368        })
369    }
370
371    /// Create a new key package for the given `ciphersuite` and `identity`.
372    ///
373    /// The HPKE init key must have been generated before and the private part
374    /// has to be stored in the key store.
375    ///
376    /// This function returns the new [`KeyPackage`] as well as the
377    /// encryption key ([`HpkeKeyPair`]) of the leaf node.
378    ///
379    /// The caller is responsible for storing the new values.
380    fn new_from_keys(
381        ciphersuite: Ciphersuite,
382        provider: &impl OpenMlsProvider,
383        signer: &impl Signer,
384        credential_with_key: CredentialWithKey,
385        extensions: Extensions<KeyPackage>,
386        leaf_node_params: KeyPackageLeafNodeParams,
387        init_key: InitKey,
388    ) -> Result<(Self, EncryptionKeyPair), KeyPackageNewError> {
389        // We don't need the private key here. It's stored in the key store for
390        // use later when creating a group with this key package.
391
392        let KeyPackageLeafNodeParams {
393            lifetime,
394            mut capabilities,
395            extensions: leaf_node_extensions,
396            capabilities_policy,
397        } = leaf_node_params;
398
399        if matches!(capabilities_policy, CapabilitiesPolicy::Widen) {
400            capabilities.widen_for_key_package(&extensions);
401        }
402
403        let new_leaf_node_params = NewLeafNodeParams {
404            ciphersuite,
405            credential_with_key,
406            leaf_node_source: LeafNodeSource::KeyPackage(lifetime),
407            capabilities,
408            extensions: leaf_node_extensions,
409            tree_info_tbs: TreeInfoTbs::KeyPackage,
410            // A bare KeyPackage has no group context to source required
411            // capabilities from.
412            constraints: LeafNodeConstraints::default(),
413            capabilities_policy,
414        };
415
416        let (leaf_node, encryption_key_pair) =
417            LeafNode::new(provider, signer, new_leaf_node_params)?;
418
419        let key_package_tbs = KeyPackageTbs {
420            protocol_version: ProtocolVersion::default(),
421            ciphersuite,
422            init_key,
423            leaf_node,
424            extensions,
425        };
426
427        let key_package = key_package_tbs.sign(signer)?;
428
429        Ok((key_package, encryption_key_pair))
430    }
431
432    /// Create a new KeyPackage from caller-provided init and encryption keys.
433    ///
434    /// Mirrors [`KeyPackage::new_from_keys`] but takes a caller-provided
435    /// encryption key pair for the leaf instead of generating a fresh one.
436    /// Used by the virtual-clients KeyPackage build path, where both keys are
437    /// derived from a per-operation secret so a sibling can reproduce them.
438    #[cfg(feature = "virtual-clients-draft")]
439    fn new_from_vc_keys(
440        ciphersuite: Ciphersuite,
441        signer: &impl Signer,
442        credential_with_key: CredentialWithKey,
443        extensions: Extensions<KeyPackage>,
444        leaf_node_params: KeyPackageLeafNodeParams,
445        init_key: InitKey,
446        encryption_key_pair: EncryptionKeyPair,
447    ) -> Result<(Self, EncryptionKeyPair), KeyPackageNewError> {
448        let KeyPackageLeafNodeParams {
449            lifetime,
450            mut capabilities,
451            extensions: leaf_node_extensions,
452            capabilities_policy,
453        } = leaf_node_params;
454
455        if matches!(capabilities_policy, CapabilitiesPolicy::Widen) {
456            capabilities.widen_for_key_package(&extensions);
457        }
458
459        let new_leaf_node_params = NewLeafNodeParams {
460            ciphersuite,
461            credential_with_key,
462            leaf_node_source: LeafNodeSource::KeyPackage(lifetime),
463            capabilities,
464            extensions: leaf_node_extensions,
465            tree_info_tbs: TreeInfoTbs::KeyPackage,
466            // A bare KeyPackage has no group context to source required
467            // capabilities from.
468            constraints: LeafNodeConstraints::default(),
469            capabilities_policy,
470        };
471
472        let (leaf_node, encryption_key_pair) = LeafNode::new_with_encryption_key_pair(
473            signer,
474            new_leaf_node_params,
475            encryption_key_pair,
476        )?;
477
478        let key_package_tbs = KeyPackageTbs {
479            protocol_version: ProtocolVersion::default(),
480            ciphersuite,
481            init_key,
482            leaf_node,
483            extensions,
484        };
485
486        let key_package = key_package_tbs.sign(signer)?;
487
488        Ok((key_package, encryption_key_pair))
489    }
490
491    /// Get a reference to the extensions of this key package.
492    pub fn extensions(&self) -> &Extensions<KeyPackage> {
493        &self.payload.extensions
494    }
495
496    /// Check whether the this key package supports all the required extensions
497    /// in the provided list.
498    pub fn check_extension_support(
499        &self,
500        required_extensions: &[ExtensionType],
501    ) -> Result<(), KeyPackageExtensionSupportError> {
502        for required_extension in required_extensions.iter() {
503            if !self.extensions().contains(*required_extension) {
504                return Err(KeyPackageExtensionSupportError::UnsupportedExtension);
505            }
506        }
507
508        Ok(())
509    }
510
511    /// Compute the [`KeyPackageRef`] of this [`KeyPackage`].
512    /// The [`KeyPackageRef`] is used to identify a new member that should get
513    /// added to a group.
514    pub fn hash_ref(&self, crypto: &impl OpenMlsCrypto) -> Result<KeyPackageRef, LibraryError> {
515        make_key_package_ref(
516            &self
517                .tls_serialize_detached()
518                .map_err(LibraryError::missing_bound_check)?,
519            self.payload.ciphersuite,
520            crypto,
521        )
522        .map_err(LibraryError::unexpected_crypto_error)
523    }
524
525    /// Get the [`Ciphersuite`].
526    pub fn ciphersuite(&self) -> Ciphersuite {
527        self.payload.ciphersuite
528    }
529
530    /// Get the [`LeafNode`] reference.
531    pub fn leaf_node(&self) -> &LeafNode {
532        &self.payload.leaf_node
533    }
534
535    /// Get the public HPKE init key of this key package.
536    pub fn hpke_init_key(&self) -> &InitKey {
537        &self.payload.init_key
538    }
539
540    /// Check if this KeyPackage is a last-resort KeyPackage.
541    ///
542    /// When the `extensions-draft` feature is enabled, this recognizes the
543    /// `last_resort_key_package` component in the KeyPackage's
544    /// `app_data_dictionary`. The obsolete `last_resort` extension is also
545    /// recognized so that previously stored KeyPackages remain usable.
546    pub fn last_resort(&self) -> bool {
547        has_last_resort_marker(&self.payload.extensions)
548    }
549
550    #[cfg(feature = "extensions-draft")]
551    fn has_malformed_last_resort_component(&self) -> bool {
552        matches!(
553            self.payload
554                .extensions
555                .app_data_dictionary()
556                .and_then(|extension| extension.dictionary().get(&last_resort_component_id())),
557            Some(data) if !data.is_empty()
558        )
559    }
560
561    /// Get the lifetime of the KeyPackage
562    pub fn life_time(&self) -> &Lifetime {
563        // Leaf nodes contain a lifetime if an only if they are inside a KeyPackage. Since we are
564        // in a KeyPackage, this can never be None and unwrap is safe.
565        // TODO: get rid of the unwrap, see https://github.com/openmls/openmls/issues/1663.
566        self.payload.leaf_node.life_time().unwrap()
567    }
568}
569
570/// Crate visible `KeyPackage` functions.
571impl KeyPackage {
572    /// Get the `ProtocolVersion`.
573    pub(crate) fn protocol_version(&self) -> ProtocolVersion {
574        self.payload.protocol_version
575    }
576}
577
578/// Builder that helps creating (and configuring) a [`KeyPackage`].
579#[derive(Default, Debug, Clone, Serialize, Deserialize)]
580pub struct KeyPackageBuilder {
581    key_package_lifetime: Option<Lifetime>,
582    key_package_extensions: Option<Extensions<KeyPackage>>,
583    leaf_node_capabilities: Option<Capabilities>,
584    leaf_node_extensions: Option<Extensions<LeafNode>>,
585    last_resort: bool,
586    capabilities_policy: Option<CapabilitiesPolicy>,
587}
588
589impl KeyPackageBuilder {
590    /// Create a key package builder.
591    pub fn new() -> Self {
592        Self {
593            key_package_lifetime: None,
594            key_package_extensions: None,
595            leaf_node_capabilities: None,
596            leaf_node_extensions: None,
597            last_resort: false,
598            capabilities_policy: None,
599        }
600    }
601
602    /// Set the key package lifetime.
603    pub fn key_package_lifetime(mut self, lifetime: Lifetime) -> Self {
604        self.key_package_lifetime.replace(lifetime);
605        self
606    }
607
608    /// Set the key package extensions.
609    pub fn key_package_extensions(mut self, extensions: Extensions<KeyPackage>) -> Self {
610        self.key_package_extensions.replace(extensions);
611        self
612    }
613
614    /// Mark the KeyPackage as a last-resort KeyPackage.
615    ///
616    /// With the `extensions-draft` feature enabled, this adds the
617    /// `last_resort_key_package` component to the KeyPackage's
618    /// `app_data_dictionary`. Otherwise, it adds the legacy `last_resort`
619    /// extension.
620    ///
621    /// The leaf node capabilities must advertise support for the corresponding
622    /// extension. Unset capabilities, or capabilities under
623    /// [`CapabilitiesPolicy::Widen`], are widened to include it. Otherwise,
624    /// building fails with [`KeyPackageNewError::MissingLastResortCapability`].
625    pub fn mark_as_last_resort(mut self) -> Self {
626        self.last_resort = true;
627        self
628    }
629
630    /// Set the leaf node capabilities.
631    pub fn leaf_node_capabilities(mut self, capabilities: Capabilities) -> Self {
632        self.leaf_node_capabilities.replace(capabilities);
633        self
634    }
635
636    /// Set the leaf node extensions.
637    ///
638    /// Returns an error if one or more of the extensions is invalid in leaf nodes.
639    pub fn leaf_node_extensions(mut self, extensions: Extensions<LeafNode>) -> Self {
640        self.leaf_node_extensions.replace(extensions);
641        self
642    }
643
644    /// Set how the leaf node's capabilities are treated when they don't cover
645    /// what the leaf itself or the KeyPackage's extensions use.
646    ///
647    /// If never called, capabilities set via
648    /// [`KeyPackageBuilder::leaf_node_capabilities`] are held to exactly what
649    /// was listed and unset capabilities are derived from the leaf.
650    pub fn capabilities_policy(mut self, policy: CapabilitiesPolicy) -> Self {
651        self.capabilities_policy = Some(policy);
652        self
653    }
654
655    /// Under [`CapabilitiesPolicy::Reject`], fail early if the leaf node
656    /// capabilities don't advertise the extension that carries the last-resort
657    /// marker. Under [`CapabilitiesPolicy::Widen`], it is added along with the
658    /// KeyPackage's other extensions.
659    fn ensure_last_resort_capability(
660        &self,
661        extension_type: ExtensionType,
662    ) -> Result<(), KeyPackageNewError> {
663        let (capabilities, capabilities_policy) = resolve_capabilities(
664            self.leaf_node_capabilities.clone(),
665            self.capabilities_policy,
666        );
667        if matches!(capabilities_policy, CapabilitiesPolicy::Reject)
668            && !capabilities.extensions().contains(&extension_type)
669        {
670            return Err(KeyPackageNewError::MissingLastResortCapability(
671                extension_type,
672            ));
673        }
674        Ok(())
675    }
676
677    /// Ensure that a last-resort marker is present in the KeyPackage if the
678    /// `last_resort` flag is set.
679    ///
680    /// A `last_resort` extension the caller set explicitly is kept alongside
681    /// the component, so that applications can emit both while not all
682    /// clients understand the component yet.
683    #[cfg(feature = "extensions-draft")]
684    fn ensure_last_resort(&mut self) -> Result<(), KeyPackageNewError> {
685        if !self.last_resort {
686            return Ok(());
687        }
688
689        self.ensure_last_resort_capability(ExtensionType::AppDataDictionary)?;
690
691        let mut dictionary = self
692            .key_package_extensions
693            .as_ref()
694            .and_then(Extensions::app_data_dictionary)
695            .map(|extension| extension.dictionary().clone())
696            .unwrap_or_default();
697        dictionary.insert(last_resort_component_id(), Vec::new());
698
699        let last_resort_component =
700            Extension::AppDataDictionary(AppDataDictionaryExtension::new(dictionary));
701        if let Some(extensions) = self.key_package_extensions.as_mut() {
702            extensions
703                .add_or_replace(last_resort_component)
704                .expect("AppDataDictionary extensions are allowed in KeyPackages");
705        } else {
706            self.key_package_extensions = Some(
707                Extensions::single(last_resort_component)
708                    .expect("AppDataDictionary extensions are allowed in KeyPackages"),
709            );
710        }
711        Ok(())
712    }
713
714    #[cfg(not(feature = "extensions-draft"))]
715    fn ensure_last_resort(&mut self) -> Result<(), KeyPackageNewError> {
716        if !self.last_resort {
717            return Ok(());
718        }
719
720        self.ensure_last_resort_capability(ExtensionType::LastResort)?;
721
722        let last_resort_extension = Extension::LastResort(LastResortExtension::default());
723        if let Some(extensions) = self.key_package_extensions.as_mut() {
724            extensions
725                .add_or_replace(last_resort_extension)
726                .expect("LastResort extensions are allowed in KeyPackages");
727        } else {
728            self.key_package_extensions = Some(
729                Extensions::single(last_resort_extension)
730                    .expect("LastResort extensions are allowed in KeyPackages"),
731            );
732        }
733        Ok(())
734    }
735
736    #[cfg(test)]
737    pub(crate) fn build_without_storage(
738        mut self,
739        ciphersuite: Ciphersuite,
740        provider: &impl OpenMlsProvider,
741        signer: &impl Signer,
742        credential_with_key: CredentialWithKey,
743    ) -> Result<KeyPackageCreationResult, KeyPackageNewError> {
744        self.ensure_last_resort()?;
745        let (capabilities, capabilities_policy) =
746            resolve_capabilities(self.leaf_node_capabilities, self.capabilities_policy);
747        let leaf_node_params = KeyPackageLeafNodeParams {
748            lifetime: self.key_package_lifetime.unwrap_or_default(),
749            capabilities,
750            extensions: self.leaf_node_extensions.unwrap_or_default(),
751            capabilities_policy,
752        };
753        KeyPackage::create(
754            ciphersuite,
755            provider,
756            signer,
757            credential_with_key,
758            self.key_package_extensions.unwrap_or_default(),
759            leaf_node_params,
760        )
761    }
762
763    /// Finalize and build the key package.
764    #[openmls_traits::maybe_async]
765    pub async fn build(
766        mut self,
767        ciphersuite: Ciphersuite,
768        provider: &impl OpenMlsProvider,
769        signer: &impl Signer,
770        credential_with_key: CredentialWithKey,
771    ) -> Result<KeyPackageBundle, KeyPackageNewError> {
772        self.ensure_last_resort()?;
773
774        let (capabilities, capabilities_policy) =
775            resolve_capabilities(self.leaf_node_capabilities, self.capabilities_policy);
776        let leaf_node_params = KeyPackageLeafNodeParams {
777            lifetime: self.key_package_lifetime.unwrap_or_default(),
778            capabilities,
779            extensions: self.leaf_node_extensions.unwrap_or_default(),
780            capabilities_policy,
781        };
782        let KeyPackageCreationResult {
783            key_package,
784            encryption_keypair,
785            init_private_key,
786        } = KeyPackage::create(
787            ciphersuite,
788            provider,
789            signer,
790            credential_with_key,
791            self.key_package_extensions.unwrap_or_default(),
792            leaf_node_params,
793        )?;
794
795        // Store the key package in the key store with the hash reference as id
796        // for retrieval when parsing welcome messages.
797        let full_kp = KeyPackageBundle {
798            key_package,
799            private_init_key: init_private_key,
800            private_encryption_key: encryption_keypair.private_key().clone(),
801        };
802        provider
803            .storage()
804            .write_key_package(&full_kp.key_package.hash_ref(provider.crypto())?, &full_kp)
805            .await
806            .map_err(|_| KeyPackageNewError::StorageError)?;
807
808        Ok(full_kp)
809    }
810
811    /// Build a batch of virtual-client KeyPackages a sibling can reproduce.
812    ///
813    /// The batch uses the newest derivation epoch of the emulation group named
814    /// by `emulation_group_id`, which is what the draft requires of every new
815    /// virtual-client operation. The epoch is resolved from the emulation
816    /// group's current state, and the returned [`VcKeyPackageBatch`] reports it
817    /// in its `epoch_id`.
818    ///
819    /// Allocates a single generation of the `key_package` operation ratchet for
820    /// that derivation epoch. For each
821    /// `key_package_index` in `0..count` it derives a per-KeyPackage seed
822    /// secret from that one operation secret and derives the KeyPackage's init
823    /// key and leaf encryption key from the seed. Each leaf carries an
824    /// encrypted `DerivationInfo` under
825    /// [`VC_COMPONENT_ID`](crate::components::vc_derivation_info::VC_COMPONENT_ID)
826    /// so a sibling can recover the emulation leaf index, generation, and
827    /// index.
828    ///
829    /// The operation secret and the seeds are derived under the derivation
830    /// epoch's ciphersuite (the operation tree's ciphersuite). The init and
831    /// leaf-encryption keys are derived from each seed under the KeyPackage's
832    /// own `ciphersuite`.
833    ///
834    /// Each leaf must declare `AppDataDictionary` support and list
835    /// [`VC_COMPONENT_ID`](crate::components::vc_derivation_info::VC_COMPONENT_ID)
836    /// in its `AppComponents` entry, otherwise this returns
837    /// [`VirtualClientsError::AppDataDictionaryNotSupported`](crate::components::vc_derivation_info::VirtualClientsError::AppDataDictionaryNotSupported)
838    /// or
839    /// [`VirtualClientsError::VcComponentNotListed`](crate::components::vc_derivation_info::VirtualClientsError::VcComponentNotListed).
840    ///
841    /// Returns a [`VcKeyPackageBatch`] holding the batch's `generation` and one
842    /// `(KeyPackageBundle, KeyPackageInfo)` per KeyPackage. Each bundle is also
843    /// written to storage so the creating client can process its own Welcomes,
844    /// and each
845    /// [`KeyPackageInfo`](crate::components::vc_derivation_info::KeyPackageInfo)
846    /// carries the index the client hands to its sibling.
847    ///
848    /// Returns [`KeyPackageNewError::EmptyBatch`] when `count` is 0, before
849    /// loading any state or consuming a generation.
850    #[cfg(feature = "virtual-clients-draft")]
851    #[openmls_traits::maybe_async]
852    pub async fn build_vc_batch(
853        self,
854        ciphersuite: Ciphersuite,
855        provider: &impl OpenMlsProvider,
856        signer: &impl Signer,
857        credential_with_key: CredentialWithKey,
858        emulation_group_id: &crate::group::GroupId,
859        count: usize,
860    ) -> Result<VcKeyPackageBatch, KeyPackageNewError> {
861        // Reject an unsupported ciphersuite and an empty batch before loading
862        // state or consuming a generation.
863        provider
864            .crypto()
865            .supports(ciphersuite)
866            .map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
867
868        if count == 0 {
869            return Err(KeyPackageNewError::EmptyBatch);
870        }
871        let mut builder =
872            VcKeyPackageBatchBuilder::with_capacity(provider, emulation_group_id, count).await?;
873        for _ in 0..count {
874            builder.add_key_package(
875                self.clone(),
876                ciphersuite,
877                provider.crypto(),
878                signer,
879                credential_with_key.clone(),
880            )?;
881        }
882        builder.finalize(provider).await
883    }
884}
885
886/// A [`KeyPackageBundle`] contains a [`KeyPackage`] and the init and encryption
887/// private key.
888///
889/// This is stored to ensure the private key is handled together with the key
890/// package.
891#[derive(Debug, Clone, Serialize, Deserialize)]
892pub struct KeyPackageBundle {
893    pub(crate) key_package: KeyPackage,
894    pub(crate) private_init_key: HpkePrivateKey,
895    pub(crate) private_encryption_key: EncryptionPrivateKey,
896}
897
898// Public `KeyPackageBundle` functions.
899impl KeyPackageBundle {
900    /// Get a reference to the public part of this bundle, i.e. the [`KeyPackage`].
901    pub fn key_package(&self) -> &KeyPackage {
902        &self.key_package
903    }
904
905    /// Extract the key package from the bundle.
906    pub fn into_key_package(self) -> KeyPackage {
907        self.key_package
908    }
909
910    /// Get a reference to the private init key.
911    pub fn init_private_key(&self) -> &HpkePrivateKey {
912        &self.private_init_key
913    }
914
915    /// Get the encryption key pair.
916    pub(crate) fn encryption_key_pair(&self) -> EncryptionKeyPair {
917        EncryptionKeyPair::from((
918            self.key_package.leaf_node().encryption_key().clone(),
919            self.private_encryption_key.clone(),
920        ))
921    }
922}
923
924#[cfg(any(test, feature = "test-utils"))]
925impl KeyPackageBundle {
926    /// Generate a new key package bundle with the private key.
927    pub fn new(
928        key_package: KeyPackage,
929        private_init_key: HpkePrivateKey,
930        private_encryption_key: EncryptionPrivateKey,
931    ) -> Self {
932        Self {
933            key_package,
934            private_init_key,
935            private_encryption_key,
936        }
937    }
938
939    /// Get a reference to the private encryption key.
940    pub fn encryption_private_key(&self) -> &HpkePrivateKey {
941        self.private_encryption_key.key()
942    }
943}
944
945#[cfg(test)]
946impl KeyPackageBundle {
947    pub(crate) fn generate(
948        provider: &impl OpenMlsProvider,
949        signer: &impl Signer,
950        ciphersuite: Ciphersuite,
951        credential_with_key: CredentialWithKey,
952    ) -> Self {
953        KeyPackage::builder()
954            .leaf_node_capabilities(
955                Capabilities::builder()
956                    .ciphersuites(vec![ciphersuite])
957                    .credentials(vec![CredentialType::Basic])
958                    .build(),
959            )
960            .build(ciphersuite, provider, signer, credential_with_key)
961            .unwrap()
962    }
963}