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