Skip to main content

openmls/group/mls_group/
exporting.rs

1use errors::{ExportGroupInfoError, ExportSecretError};
2use openmls_traits::{crypto::OpenMlsCrypto, signatures::Signer};
3
4use crate::{
5    ciphersuite::HpkePublicKey,
6    extensions::errors::InvalidExtensionError,
7    schedule::{EpochAuthenticator, ResumptionPskSecret},
8};
9#[cfg(feature = "extensions-draft")]
10use crate::{
11    component::ComponentId,
12    group::{PendingSafeExportSecretError, SafeExportSecretError},
13};
14
15#[cfg(feature = "virtual-clients-draft")]
16use crate::{
17    components::vc_derivation_info::{
18        EmulationEpochState, EmulatorEpochSecret, EpochId, VC_COMPONENT_ID,
19    },
20    components::vc_operation_tree::OperationSecretTree,
21    group::mls_group::errors::RegisterVcEmulationEpochError,
22};
23
24use super::*;
25
26impl MlsGroup {
27    // === Export secrets ===
28
29    /// Exports a secret from the current epoch.
30    /// Returns [`ExportSecretError::KeyLengthTooLong`] if the requested
31    /// key length is too long.
32    /// Returns [`ExportSecretError::GroupStateError(MlsGroupStateError::UseAfterEviction)`](MlsGroupStateError::UseAfterEviction)
33    /// if the group is not active.
34    pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
35        &self,
36        crypto: &CryptoProvider,
37        label: &str,
38        context: &[u8],
39        key_length: usize,
40    ) -> Result<Vec<u8>, ExportSecretError> {
41        if key_length > u16::MAX as usize {
42            log::error!("Got a key that is larger than u16::MAX");
43            return Err(ExportSecretError::KeyLengthTooLong);
44        }
45
46        if self.is_active() {
47            Ok(self
48                .group_epoch_secrets
49                .exporter_secret()
50                .derive_exported_secret(self.ciphersuite(), crypto, label, context, key_length)
51                .map_err(LibraryError::unexpected_crypto_error)?)
52        } else {
53            Err(ExportSecretError::GroupStateError(
54                MlsGroupStateError::UseAfterEviction,
55            ))
56        }
57    }
58
59    /// Export a secret from the forward secure exporter for the component with
60    /// the given component ID.
61    #[cfg(feature = "extensions-draft")]
62    pub fn safe_export_secret<Crypto: OpenMlsCrypto, Storage: StorageProvider>(
63        &mut self,
64        crypto: &Crypto,
65        storage: &Storage,
66        component_id: ComponentId,
67    ) -> Result<Vec<u8>, SafeExportSecretError<Storage::Error>> {
68        if !self.is_active() {
69            return Err(SafeExportSecretError::GroupState(
70                MlsGroupStateError::UseAfterEviction,
71            ));
72        }
73        let group_id = self.public_group.group_id();
74        let ciphersuite = self.ciphersuite();
75        let Some(application_export_tree) = self.application_export_tree.as_mut() else {
76            return Err(SafeExportSecretError::Unsupported);
77        };
78        let component_secret =
79            application_export_tree.safe_export_secret(crypto, ciphersuite, component_id)?;
80        storage
81            .write_application_export_tree(group_id, application_export_tree)
82            .map_err(SafeExportSecretError::Storage)?;
83
84        Ok(component_secret.as_slice().to_vec())
85    }
86
87    /// Export a secret from the forward secure exporter of the pending commit
88    /// state for the component with the given component ID.
89    #[cfg(feature = "extensions-draft")]
90    pub fn safe_export_secret_from_pending<Provider: StorageProvider>(
91        &mut self,
92        crypto: &impl OpenMlsCrypto,
93        storage: &Provider,
94        component_id: ComponentId,
95    ) -> Result<Vec<u8>, PendingSafeExportSecretError<Provider::Error>> {
96        let group_id = self.group_id().clone();
97        let MlsGroupState::PendingCommit(ref mut group_state) = self.group_state else {
98            return Err(PendingSafeExportSecretError::NoPendingCommit);
99        };
100        let PendingCommitState::Member(ref mut staged_commit) = **group_state else {
101            return Err(PendingSafeExportSecretError::NotGroupMember);
102        };
103        let secret = staged_commit.safe_export_secret(crypto, component_id)?;
104        storage
105            .write_group_state(&group_id, &self.group_state)
106            .map_err(PendingSafeExportSecretError::Storage)?;
107        Ok(secret.as_slice().to_vec())
108    }
109
110    /// Register a new virtual-clients emulation epoch for this *emulation*
111    /// group.
112    ///
113    /// Sources the per-emulation-epoch root secret from
114    /// `self.safe_export_secret(crypto, storage, VC_COMPONENT_ID)`,
115    /// derives the [`EpochId`], the AEAD key, and the epoch base secret,
116    /// builds the per-epoch operation secret tree (sized like the emulation
117    /// group's ratchet tree), and persists the tree and the per-epoch state
118    /// in the storage provider keyed on the derived `EpochId`. Returns the
119    /// `EpochId` so the caller can reference this emulation epoch on
120    /// subsequent virtual-clients commits.
121    ///
122    /// The emulation group must support `safe_export_secret`, which requires
123    /// the appropriate `AppDataDictionary` capability and extension wiring at
124    /// group creation. Otherwise this returns
125    /// [`SafeExportSecretError::Unsupported`] via
126    /// [`RegisterVcEmulationEpochError::SafeExportSecret`].
127    #[cfg(feature = "virtual-clients-draft")]
128    pub fn register_vc_emulation_epoch<Crypto: OpenMlsCrypto, Storage: StorageProvider>(
129        &mut self,
130        crypto: &Crypto,
131        storage: &Storage,
132    ) -> Result<EpochId, RegisterVcEmulationEpochError<Storage::Error>> {
133        let ciphersuite = self.ciphersuite();
134        let leaf_index = self.own_leaf_index();
135        let emulation_group_size = self.public_group().tree_size();
136        let bytes = self.safe_export_secret(crypto, storage, VC_COMPONENT_ID)?;
137        let emulator_epoch_secret = EmulatorEpochSecret::new(&bytes);
138        let epoch_id = emulator_epoch_secret.derive_epoch_id(crypto, ciphersuite)?;
139        let epoch_encryption_key =
140            emulator_epoch_secret.derive_epoch_encryption_key(crypto, ciphersuite)?;
141        let epoch_base_secret =
142            emulator_epoch_secret.derive_epoch_base_secret(crypto, ciphersuite)?;
143        let reuse_guard_secret =
144            emulator_epoch_secret.derive_reuse_guard_secret(crypto, ciphersuite)?;
145        let generation_id_secret =
146            emulator_epoch_secret.derive_generation_id_secret(crypto, ciphersuite)?;
147        let operation_tree = OperationSecretTree::new(epoch_base_secret, emulation_group_size);
148        let state = EmulationEpochState::new(
149            leaf_index,
150            epoch_encryption_key,
151            reuse_guard_secret,
152            generation_id_secret,
153            emulation_group_size,
154            ciphersuite,
155        );
156
157        storage
158            .write_vc_operation_tree(&epoch_id, &operation_tree)
159            .map_err(|e| {
160                log::error!(
161                    "vc: persist operation tree in register_vc_emulation_epoch failed: {e:?}"
162                );
163                RegisterVcEmulationEpochError::Storage(e)
164            })?;
165        storage
166            .write_vc_emulation_epoch_state(&epoch_id, &state)
167            .map_err(|e| {
168                log::error!(
169                    "vc: persist emulation epoch state in register_vc_emulation_epoch failed: {e:?}"
170                );
171                RegisterVcEmulationEpochError::Storage(e)
172            })?;
173
174        Ok(epoch_id)
175    }
176
177    /// Returns the epoch authenticator of the current epoch.
178    pub fn epoch_authenticator(&self) -> &EpochAuthenticator {
179        self.group_epoch_secrets().epoch_authenticator()
180    }
181
182    /// Returns the resumption PSK secret of the current epoch.
183    pub fn resumption_psk_secret(&self) -> &ResumptionPskSecret {
184        self.group_epoch_secrets().resumption_psk()
185    }
186
187    /// Returns a resumption psk for a given epoch. If no resumption psk
188    /// is available for that epoch,  `None` is returned.
189    pub fn get_past_resumption_psk(&self, epoch: GroupEpoch) -> Option<&ResumptionPskSecret> {
190        self.resumption_psk_store.get(epoch)
191    }
192
193    /// Export a group info object for this group.
194    pub fn export_group_info<CryptoProvider: OpenMlsCrypto>(
195        &self,
196        crypto: &CryptoProvider,
197        signer: &impl Signer,
198        with_ratchet_tree: bool,
199    ) -> Result<MlsMessageOut, ExportGroupInfoError> {
200        self.export_group_info_with_additional_extensions(crypto, signer, with_ratchet_tree, None)
201    }
202
203    /// Export a group info object for this group, with additional extensions.
204    ///
205    ///  Returns an error if a  [`RatchetTreeExtension`] or [`ExternalPubExtension`] is added
206    ///  directly here.
207    pub fn export_group_info_with_additional_extensions<CryptoProvider: OpenMlsCrypto>(
208        &self,
209        crypto: &CryptoProvider,
210        signer: &impl Signer,
211        with_ratchet_tree: bool,
212        additional_extensions: impl IntoIterator<Item = Extension>,
213    ) -> Result<MlsMessageOut, ExportGroupInfoError> {
214        let extensions = {
215            let ratchet_tree_extension = || {
216                Extension::RatchetTree(RatchetTreeExtension::new(
217                    self.public_group().export_ratchet_tree(),
218                ))
219            };
220
221            let external_pub_extension = || -> Result<Extension, ExportGroupInfoError> {
222                let external_pub = self
223                    .group_epoch_secrets()
224                    .external_secret()
225                    .derive_external_keypair(crypto, self.ciphersuite())
226                    .map_err(LibraryError::unexpected_crypto_error)?
227                    .public;
228                Ok(Extension::ExternalPub(ExternalPubExtension::new(
229                    HpkePublicKey::from(external_pub),
230                )))
231            };
232
233            let mut extensions = if with_ratchet_tree {
234                vec![ratchet_tree_extension(), external_pub_extension()?]
235            } else {
236                vec![external_pub_extension()?]
237            };
238
239            extensions.extend(
240                additional_extensions
241                    .into_iter()
242                    .map(|extension| {
243                        if extension.as_ratchet_tree_extension().is_ok()
244                            || extension.as_external_pub_extension().is_ok()
245                        {
246                            Err(InvalidExtensionError::CannotAddDirectlyToGroupInfo)
247                        } else {
248                            Ok(extension)
249                        }
250                    })
251                    .collect::<Result<Vec<_>, _>>()?,
252            );
253
254            Extensions::from_vec(extensions)?
255        };
256
257        // Create to-be-signed group info.
258        let group_info_tbs = GroupInfoTBS::new(
259            self.context().clone(),
260            extensions,
261            self.message_secrets()
262                .confirmation_key()
263                .tag(
264                    crypto,
265                    self.ciphersuite(),
266                    self.context().confirmed_transcript_hash(),
267                )
268                .map_err(LibraryError::unexpected_crypto_error)?,
269            self.own_leaf_index(),
270        )?;
271
272        // Sign to-be-signed group info.
273        let group_info = group_info_tbs
274            .sign(signer)
275            .map_err(|_| LibraryError::custom("Signing failed"))?;
276        Ok(group_info.into())
277    }
278}