Skip to main content

openmls/group/mls_group/
exporting.rs

1use errors::{ExportGroupInfoError, ExportSecretError};
2use openmls_traits::{crypto::OpenMlsCrypto, signatures::Signer};
3
4#[cfg(feature = "extensions-draft-08")]
5use crate::group::{PendingSafeExportSecretError, SafeExportSecretError};
6use crate::{
7    ciphersuite::HpkePublicKey,
8    extensions::errors::InvalidExtensionError,
9    schedule::{EpochAuthenticator, ResumptionPskSecret},
10};
11
12use super::*;
13
14impl MlsGroup {
15    // === Export secrets ===
16
17    /// Exports a secret from the current epoch.
18    /// Returns [`ExportSecretError::KeyLengthTooLong`] if the requested
19    /// key length is too long.
20    /// Returns [`ExportSecretError::GroupStateError(MlsGroupStateError::UseAfterEviction)`](MlsGroupStateError::UseAfterEviction)
21    /// if the group is not active.
22    pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
23        &self,
24        crypto: &CryptoProvider,
25        label: &str,
26        context: &[u8],
27        key_length: usize,
28    ) -> Result<Vec<u8>, ExportSecretError> {
29        if key_length > u16::MAX as usize {
30            log::error!("Got a key that is larger than u16::MAX");
31            return Err(ExportSecretError::KeyLengthTooLong);
32        }
33
34        if self.is_active() {
35            Ok(self
36                .group_epoch_secrets
37                .exporter_secret()
38                .derive_exported_secret(self.ciphersuite(), crypto, label, context, key_length)
39                .map_err(LibraryError::unexpected_crypto_error)?)
40        } else {
41            Err(ExportSecretError::GroupStateError(
42                MlsGroupStateError::UseAfterEviction,
43            ))
44        }
45    }
46
47    /// Export a secret from the forward secure exporter for the component with
48    /// the given component ID.
49    #[cfg(feature = "extensions-draft-08")]
50    pub fn safe_export_secret<Crypto: OpenMlsCrypto, Storage: StorageProvider>(
51        &mut self,
52        crypto: &Crypto,
53        storage: &Storage,
54        component_id: u16,
55    ) -> Result<Vec<u8>, SafeExportSecretError<Storage::Error>> {
56        if !self.is_active() {
57            return Err(SafeExportSecretError::GroupState(
58                MlsGroupStateError::UseAfterEviction,
59            ));
60        }
61        let group_id = self.public_group.group_id();
62        let ciphersuite = self.ciphersuite();
63        let Some(application_export_tree) = self.application_export_tree.as_mut() else {
64            return Err(SafeExportSecretError::Unsupported);
65        };
66        let component_secret =
67            application_export_tree.safe_export_secret(crypto, ciphersuite, component_id)?;
68        storage
69            .write_application_export_tree(group_id, application_export_tree)
70            .map_err(SafeExportSecretError::Storage)?;
71
72        Ok(component_secret.as_slice().to_vec())
73    }
74
75    /// Export a secret from the forward secure exporter of the pending commit
76    /// state for the component with the given component ID.
77    #[cfg(feature = "extensions-draft-08")]
78    pub fn safe_export_secret_from_pending<Provider: StorageProvider>(
79        &mut self,
80        crypto: &impl OpenMlsCrypto,
81        storage: &Provider,
82        component_id: u16,
83    ) -> Result<Vec<u8>, PendingSafeExportSecretError<Provider::Error>> {
84        let group_id = self.group_id().clone();
85        let MlsGroupState::PendingCommit(ref mut group_state) = self.group_state else {
86            return Err(PendingSafeExportSecretError::NoPendingCommit);
87        };
88        let PendingCommitState::Member(ref mut staged_commit) = **group_state else {
89            return Err(PendingSafeExportSecretError::NotGroupMember);
90        };
91        let secret = staged_commit.safe_export_secret(crypto, component_id)?;
92        storage
93            .write_group_state(&group_id, &self.group_state)
94            .map_err(PendingSafeExportSecretError::Storage)?;
95        Ok(secret.as_slice().to_vec())
96    }
97
98    /// Returns the epoch authenticator of the current epoch.
99    pub fn epoch_authenticator(&self) -> &EpochAuthenticator {
100        self.group_epoch_secrets().epoch_authenticator()
101    }
102
103    /// Returns the resumption PSK secret of the current epoch.
104    pub fn resumption_psk_secret(&self) -> &ResumptionPskSecret {
105        self.group_epoch_secrets().resumption_psk()
106    }
107
108    /// Returns a resumption psk for a given epoch. If no resumption psk
109    /// is available for that epoch,  `None` is returned.
110    pub fn get_past_resumption_psk(&self, epoch: GroupEpoch) -> Option<&ResumptionPskSecret> {
111        self.resumption_psk_store.get(epoch)
112    }
113
114    /// Export a group info object for this group.
115    pub fn export_group_info<CryptoProvider: OpenMlsCrypto>(
116        &self,
117        crypto: &CryptoProvider,
118        signer: &impl Signer,
119        with_ratchet_tree: bool,
120    ) -> Result<MlsMessageOut, ExportGroupInfoError> {
121        self.export_group_info_with_additional_extensions(crypto, signer, with_ratchet_tree, None)
122    }
123
124    /// Export a group info object for this group, with additional extensions.
125    ///
126    ///  Returns an error if a  [`RatchetTreeExtension`] or [`ExternalPubExtension`] is added
127    ///  directly here.
128    pub fn export_group_info_with_additional_extensions<CryptoProvider: OpenMlsCrypto>(
129        &self,
130        crypto: &CryptoProvider,
131        signer: &impl Signer,
132        with_ratchet_tree: bool,
133        additional_extensions: impl IntoIterator<Item = Extension>,
134    ) -> Result<MlsMessageOut, ExportGroupInfoError> {
135        let extensions = {
136            let ratchet_tree_extension = || {
137                Extension::RatchetTree(RatchetTreeExtension::new(
138                    self.public_group().export_ratchet_tree(),
139                ))
140            };
141
142            let external_pub_extension = || -> Result<Extension, ExportGroupInfoError> {
143                let external_pub = self
144                    .group_epoch_secrets()
145                    .external_secret()
146                    .derive_external_keypair(crypto, self.ciphersuite())
147                    .map_err(LibraryError::unexpected_crypto_error)?
148                    .public;
149                Ok(Extension::ExternalPub(ExternalPubExtension::new(
150                    HpkePublicKey::from(external_pub),
151                )))
152            };
153
154            let mut extensions = if with_ratchet_tree {
155                vec![ratchet_tree_extension(), external_pub_extension()?]
156            } else {
157                vec![external_pub_extension()?]
158            };
159
160            extensions.extend(
161                additional_extensions
162                    .into_iter()
163                    .map(|extension| {
164                        if extension.as_ratchet_tree_extension().is_ok()
165                            || extension.as_external_pub_extension().is_ok()
166                        {
167                            Err(InvalidExtensionError::CannotAddDirectlyToGroupInfo)
168                        } else {
169                            Ok(extension)
170                        }
171                    })
172                    .collect::<Result<Vec<_>, _>>()?,
173            );
174
175            Extensions::from_vec(extensions)?
176        };
177
178        // Create to-be-signed group info.
179        let group_info_tbs = GroupInfoTBS::new(
180            self.context().clone(),
181            extensions,
182            self.message_secrets()
183                .confirmation_key()
184                .tag(
185                    crypto,
186                    self.ciphersuite(),
187                    self.context().confirmed_transcript_hash(),
188                )
189                .map_err(LibraryError::unexpected_crypto_error)?,
190            self.own_leaf_index(),
191        )?;
192
193        // Sign to-be-signed group info.
194        let group_info = group_info_tbs
195            .sign(signer)
196            .map_err(|_| LibraryError::custom("Signing failed"))?;
197        Ok(group_info.into())
198    }
199}