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