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
91use crate::{
92    ciphersuite::{
93        hash_ref::{make_key_package_ref, KeyPackageRef},
94        signable::*,
95        *,
96    },
97    credentials::*,
98    error::LibraryError,
99    extensions::{
100        errors::InvalidExtensionError, Extension, ExtensionType, Extensions, LastResortExtension,
101    },
102    storage::OpenMlsProvider,
103    treesync::{
104        node::{
105            encryption_keys::{EncryptionKeyPair, EncryptionPrivateKey},
106            leaf_node::{Capabilities, LeafNodeSource, NewLeafNodeParams, TreeInfoTbs},
107        },
108        LeafNode,
109    },
110    versions::ProtocolVersion,
111};
112use openmls_traits::{
113    crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider, types::Ciphersuite,
114};
115use serde::{Deserialize, Serialize};
116use tls_codec::{
117    Serialize as TlsSerializeTrait, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
118};
119
120// Private
121use errors::*;
122
123// Public
124pub mod errors;
125pub mod key_package_in;
126
127mod lifetime;
128
129// Tests
130#[cfg(test)]
131pub(crate) mod tests;
132
133// Public types
134pub use key_package_in::KeyPackageIn;
135pub use lifetime::Lifetime;
136
137/// The unsigned payload of a key package.
138/// Any modification must happen on this unsigned struct. Use `sign` to get a
139/// signed key package.
140///
141/// ```text
142/// struct {
143///     ProtocolVersion version;
144///     CipherSuite cipher_suite;
145///     HPKEPublicKey init_key;
146///     LeafNode leaf_node;
147///     Extension extensions<V>;
148/// } KeyPackageTBS;
149/// ```
150#[derive(Debug, Clone, PartialEq, TlsSize, TlsSerialize, Serialize, Deserialize)]
151struct KeyPackageTbs {
152    protocol_version: ProtocolVersion,
153    ciphersuite: Ciphersuite,
154    init_key: InitKey,
155    leaf_node: LeafNode,
156    extensions: Extensions,
157}
158
159impl Signable for KeyPackageTbs {
160    type SignedOutput = KeyPackage;
161
162    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
163        self.tls_serialize_detached()
164    }
165
166    fn label(&self) -> &str {
167        SIGNATURE_KEY_PACKAGE_LABEL
168    }
169}
170
171impl From<KeyPackage> for KeyPackageTbs {
172    fn from(kp: KeyPackage) -> Self {
173        kp.payload
174    }
175}
176
177/// The key package struct.
178#[derive(Debug, Clone, Serialize, Deserialize, TlsSize)]
179pub struct KeyPackage {
180    payload: KeyPackageTbs,
181    signature: Signature,
182    #[serde(skip)]
183    #[tls_codec(skip)]
184    serialized_payload: Option<Vec<u8>>,
185}
186
187impl TlsSerializeTrait for KeyPackage {
188    fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
189        let mut written = 0;
190        if let Some(ref bytes) = self.serialized_payload {
191            written += writer.write(bytes)?;
192        } else {
193            written += self.payload.tls_serialize(writer)?;
194        }
195        written += self.signature.tls_serialize(writer)?;
196        Ok(written)
197    }
198}
199
200impl PartialEq for KeyPackage {
201    fn eq(&self, other: &Self) -> bool {
202        // We ignore the signature in the comparison. The same key package
203        // may have different, valid signatures.
204        self.payload == other.payload
205    }
206}
207
208impl SignedStruct<KeyPackageTbs> for KeyPackage {
209    fn from_payload(
210        payload: KeyPackageTbs,
211        signature: Signature,
212        serialized_payload: Vec<u8>,
213    ) -> Self {
214        Self {
215            payload,
216            signature,
217            serialized_payload: Some(serialized_payload),
218        }
219    }
220}
221
222const SIGNATURE_KEY_PACKAGE_LABEL: &str = "KeyPackageTBS";
223
224/// Helper struct containing the results of building a new [`KeyPackage`].
225pub(crate) struct KeyPackageCreationResult {
226    pub key_package: KeyPackage,
227    pub encryption_keypair: EncryptionKeyPair,
228    pub init_private_key: HpkePrivateKey,
229}
230
231/// Init key for HPKE.
232#[derive(
233    Debug,
234    Clone,
235    PartialEq,
236    TlsSize,
237    TlsSerialize,
238    Serialize,
239    Deserialize,
240    TlsDeserialize,
241    TlsDeserializeBytes,
242)]
243pub struct InitKey {
244    key: HpkePublicKey,
245}
246
247impl InitKey {
248    /// Return the internal [`HpkePublicKey`].
249    pub fn key(&self) -> &HpkePublicKey {
250        &self.key
251    }
252
253    /// Return the internal [`HpkePublicKey`] as a slice.
254    pub fn as_slice(&self) -> &[u8] {
255        self.key.as_slice()
256    }
257}
258
259impl From<Vec<u8>> for InitKey {
260    fn from(key: Vec<u8>) -> Self {
261        Self {
262            key: HpkePublicKey::from(key),
263        }
264    }
265}
266
267impl From<HpkePublicKey> for InitKey {
268    fn from(key: HpkePublicKey) -> Self {
269        Self { key }
270    }
271}
272
273// Public `KeyPackage` functions.
274impl KeyPackage {
275    /// Create a key package builder.
276    ///
277    /// This is provided for convenience. You can also use [`KeyPackageBuilder::new`].
278    pub fn builder() -> KeyPackageBuilder {
279        KeyPackageBuilder::new()
280    }
281
282    #[allow(clippy::too_many_arguments)]
283    /// Create a new key package for the given `ciphersuite` and `identity`.
284    pub(crate) fn create(
285        ciphersuite: Ciphersuite,
286        provider: &impl OpenMlsProvider,
287        signer: &impl Signer,
288        credential_with_key: CredentialWithKey,
289        lifetime: Lifetime,
290        extensions: Extensions,
291        leaf_node_capabilities: Capabilities,
292        leaf_node_extensions: Extensions,
293    ) -> Result<KeyPackageCreationResult, KeyPackageNewError> {
294        if ciphersuite.signature_algorithm() != signer.signature_scheme() {
295            return Err(KeyPackageNewError::CiphersuiteSignatureSchemeMismatch);
296        }
297
298        // Create a new HPKE key pair
299        let ikm = Secret::random(ciphersuite, provider.rand())
300            .map_err(LibraryError::unexpected_crypto_error)?;
301        let init_key = provider
302            .crypto()
303            .derive_hpke_keypair(ciphersuite.hpke_config(), ikm.as_slice())
304            .map_err(|e| {
305                KeyPackageNewError::LibraryError(LibraryError::unexpected_crypto_error(e))
306            })?;
307        let (key_package, encryption_keypair) = Self::new_from_keys(
308            ciphersuite,
309            provider,
310            signer,
311            credential_with_key,
312            lifetime,
313            extensions,
314            leaf_node_capabilities,
315            leaf_node_extensions,
316            init_key.public.into(),
317        )?;
318
319        Ok(KeyPackageCreationResult {
320            key_package,
321            encryption_keypair,
322            init_private_key: init_key.private,
323        })
324    }
325
326    /// Create a new key package for the given `ciphersuite` and `identity`.
327    ///
328    /// The HPKE init key must have been generated before and the private part
329    /// has to be stored in the key store.
330    ///
331    /// This function returns the new [`KeyPackage`] as well as the
332    /// encryption key ([`HpkeKeyPair`]) of the leaf node.
333    ///
334    /// The caller is responsible for storing the new values.
335    #[allow(clippy::too_many_arguments)]
336    fn new_from_keys(
337        ciphersuite: Ciphersuite,
338        provider: &impl OpenMlsProvider,
339        signer: &impl Signer,
340        credential_with_key: CredentialWithKey,
341        lifetime: Lifetime,
342        extensions: Extensions,
343        capabilities: Capabilities,
344        leaf_node_extensions: Extensions,
345        init_key: InitKey,
346    ) -> Result<(Self, EncryptionKeyPair), KeyPackageNewError> {
347        // We don't need the private key here. It's stored in the key store for
348        // use later when creating a group with this key package.
349
350        let new_leaf_node_params = NewLeafNodeParams {
351            ciphersuite,
352            leaf_node_source: LeafNodeSource::KeyPackage(lifetime),
353            credential_with_key,
354            capabilities,
355            extensions: leaf_node_extensions,
356            tree_info_tbs: TreeInfoTbs::KeyPackage,
357        };
358
359        let (leaf_node, encryption_key_pair) =
360            LeafNode::new(provider, signer, new_leaf_node_params)?;
361
362        let key_package_tbs = KeyPackageTbs {
363            protocol_version: ProtocolVersion::default(),
364            ciphersuite,
365            init_key,
366            leaf_node,
367            extensions,
368        };
369
370        let key_package = key_package_tbs.sign(signer)?;
371
372        Ok((key_package, encryption_key_pair))
373    }
374
375    /// Get a reference to the extensions of this key package.
376    pub fn extensions(&self) -> &Extensions {
377        &self.payload.extensions
378    }
379
380    /// Check whether the this key package supports all the required extensions
381    /// in the provided list.
382    pub fn check_extension_support(
383        &self,
384        required_extensions: &[ExtensionType],
385    ) -> Result<(), KeyPackageExtensionSupportError> {
386        for required_extension in required_extensions.iter() {
387            if !self.extensions().contains(*required_extension) {
388                return Err(KeyPackageExtensionSupportError::UnsupportedExtension);
389            }
390        }
391
392        Ok(())
393    }
394
395    /// Compute the [`KeyPackageRef`] of this [`KeyPackage`].
396    /// The [`KeyPackageRef`] is used to identify a new member that should get
397    /// added to a group.
398    pub fn hash_ref(&self, crypto: &impl OpenMlsCrypto) -> Result<KeyPackageRef, LibraryError> {
399        make_key_package_ref(
400            &self
401                .tls_serialize_detached()
402                .map_err(LibraryError::missing_bound_check)?,
403            self.payload.ciphersuite,
404            crypto,
405        )
406        .map_err(LibraryError::unexpected_crypto_error)
407    }
408
409    /// Get the [`Ciphersuite`].
410    pub fn ciphersuite(&self) -> Ciphersuite {
411        self.payload.ciphersuite
412    }
413
414    /// Get the [`LeafNode`] reference.
415    pub fn leaf_node(&self) -> &LeafNode {
416        &self.payload.leaf_node
417    }
418
419    /// Get the public HPKE init key of this key package.
420    pub fn hpke_init_key(&self) -> &InitKey {
421        &self.payload.init_key
422    }
423
424    /// Check if this KeyPackage is a last resort key package.
425    pub fn last_resort(&self) -> bool {
426        self.payload.extensions.contains(ExtensionType::LastResort)
427    }
428
429    /// Get the lifetime of the KeyPackage
430    pub fn life_time(&self) -> &Lifetime {
431        // Leaf nodes contain a lifetime if an only if they are inside a KeyPackage. Since we are
432        // in a KeyPackage, this can never be None and unwrap is safe.
433        // TODO: get rid of the unwrap, see https://github.com/openmls/openmls/issues/1663.
434        self.payload.leaf_node.life_time().unwrap()
435    }
436}
437
438/// Crate visible `KeyPackage` functions.
439impl KeyPackage {
440    /// Get the `ProtocolVersion`.
441    pub(crate) fn protocol_version(&self) -> ProtocolVersion {
442        self.payload.protocol_version
443    }
444}
445
446/// Builder that helps creating (and configuring) a [`KeyPackage`].
447#[derive(Default, Debug, Clone, Serialize, Deserialize)]
448pub struct KeyPackageBuilder {
449    key_package_lifetime: Option<Lifetime>,
450    key_package_extensions: Option<Extensions>,
451    leaf_node_capabilities: Option<Capabilities>,
452    leaf_node_extensions: Option<Extensions>,
453    last_resort: bool,
454}
455
456impl KeyPackageBuilder {
457    /// Create a key package builder.
458    pub fn new() -> Self {
459        Self {
460            key_package_lifetime: None,
461            key_package_extensions: None,
462            leaf_node_capabilities: None,
463            leaf_node_extensions: None,
464            last_resort: false,
465        }
466    }
467
468    /// Set the key package lifetime.
469    pub fn key_package_lifetime(mut self, lifetime: Lifetime) -> Self {
470        self.key_package_lifetime.replace(lifetime);
471        self
472    }
473
474    /// Set the key package extensions.
475    pub fn key_package_extensions(mut self, extensions: Extensions) -> Self {
476        self.key_package_extensions.replace(extensions);
477        self
478    }
479
480    /// Mark the key package as a last-resort key package via a [`LastResortExtension`].
481    pub fn mark_as_last_resort(mut self) -> Self {
482        self.last_resort = true;
483        self
484    }
485
486    /// Set the leaf node capabilities.
487    pub fn leaf_node_capabilities(mut self, capabilities: Capabilities) -> Self {
488        self.leaf_node_capabilities.replace(capabilities);
489        self
490    }
491
492    /// Set the leaf node extensions.
493    ///
494    /// Returns an error if one or more of the extensions is invalid in leaf nodes.
495    pub fn leaf_node_extensions(
496        mut self,
497        extensions: Extensions,
498    ) -> Result<Self, InvalidExtensionError> {
499        // https://validation.openmls.tech/#valn1601
500        extensions.validate_extension_types_for_leaf_node()?;
501        self.leaf_node_extensions.replace(extensions);
502
503        Ok(self)
504    }
505
506    /// Ensure that a last-resort extension is present in the key package if the
507    /// `last_resort` flag is set.
508    fn ensure_last_resort(&mut self) {
509        if self.last_resort {
510            let last_resort_extension = Extension::LastResort(LastResortExtension::default());
511            if let Some(extensions) = self.key_package_extensions.as_mut() {
512                extensions.add_or_replace(last_resort_extension);
513            } else {
514                self.key_package_extensions = Some(Extensions::single(last_resort_extension));
515            }
516        }
517    }
518
519    #[cfg(test)]
520    pub(crate) fn build_without_storage(
521        mut self,
522        ciphersuite: Ciphersuite,
523        provider: &impl OpenMlsProvider,
524        signer: &impl Signer,
525        credential_with_key: CredentialWithKey,
526    ) -> Result<KeyPackageCreationResult, KeyPackageNewError> {
527        self.ensure_last_resort();
528        KeyPackage::create(
529            ciphersuite,
530            provider,
531            signer,
532            credential_with_key,
533            self.key_package_lifetime.unwrap_or_default(),
534            self.key_package_extensions.unwrap_or_default(),
535            self.leaf_node_capabilities.unwrap_or_default(),
536            self.leaf_node_extensions.unwrap_or_default(),
537        )
538    }
539
540    /// Finalize and build the key package.
541    pub fn build(
542        mut self,
543        ciphersuite: Ciphersuite,
544        provider: &impl OpenMlsProvider,
545        signer: &impl Signer,
546        credential_with_key: CredentialWithKey,
547    ) -> Result<KeyPackageBundle, KeyPackageNewError> {
548        self.ensure_last_resort();
549        let KeyPackageCreationResult {
550            key_package,
551            encryption_keypair,
552            init_private_key,
553        } = KeyPackage::create(
554            ciphersuite,
555            provider,
556            signer,
557            credential_with_key,
558            self.key_package_lifetime.unwrap_or_default(),
559            self.key_package_extensions.unwrap_or_default(),
560            self.leaf_node_capabilities.unwrap_or_default(),
561            self.leaf_node_extensions.unwrap_or_default(),
562        )?;
563
564        // Store the key package in the key store with the hash reference as id
565        // for retrieval when parsing welcome messages.
566        let full_kp = KeyPackageBundle {
567            key_package,
568            private_init_key: init_private_key,
569            private_encryption_key: encryption_keypair.private_key().clone(),
570        };
571        provider
572            .storage()
573            .write_key_package(&full_kp.key_package.hash_ref(provider.crypto())?, &full_kp)
574            .map_err(|_| KeyPackageNewError::StorageError)?;
575
576        Ok(full_kp)
577    }
578}
579
580/// A [`KeyPackageBundle`] contains a [`KeyPackage`] and the init and encryption
581/// private key.
582///
583/// This is stored to ensure the private key is handled together with the key
584/// package.
585#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct KeyPackageBundle {
587    pub(crate) key_package: KeyPackage,
588    pub(crate) private_init_key: HpkePrivateKey,
589    pub(crate) private_encryption_key: EncryptionPrivateKey,
590}
591
592// Public `KeyPackageBundle` functions.
593impl KeyPackageBundle {
594    /// Get a reference to the public part of this bundle, i.e. the [`KeyPackage`].
595    pub fn key_package(&self) -> &KeyPackage {
596        &self.key_package
597    }
598
599    /// Get a reference to the private init key.
600    pub fn init_private_key(&self) -> &HpkePrivateKey {
601        &self.private_init_key
602    }
603
604    /// Get the encryption key pair.
605    pub(crate) fn encryption_key_pair(&self) -> EncryptionKeyPair {
606        EncryptionKeyPair::from((
607            self.key_package.leaf_node().encryption_key().clone(),
608            self.private_encryption_key.clone(),
609        ))
610    }
611}
612
613#[cfg(any(test, feature = "test-utils"))]
614impl KeyPackageBundle {
615    /// Generate a new key package bundle with the private key.
616    pub fn new(
617        key_package: KeyPackage,
618        private_init_key: HpkePrivateKey,
619        private_encryption_key: EncryptionPrivateKey,
620    ) -> Self {
621        Self {
622            key_package,
623            private_init_key,
624            private_encryption_key,
625        }
626    }
627
628    /// Get a reference to the private encryption key.
629    pub fn encryption_private_key(&self) -> &HpkePrivateKey {
630        self.private_encryption_key.key()
631    }
632}
633
634#[cfg(test)]
635impl KeyPackageBundle {
636    pub(crate) fn generate(
637        provider: &impl OpenMlsProvider,
638        signer: &impl Signer,
639        ciphersuite: Ciphersuite,
640        credential_with_key: CredentialWithKey,
641    ) -> Self {
642        KeyPackage::builder()
643            .build(ciphersuite, provider, signer, credential_with_key)
644            .unwrap()
645    }
646}