openmls/group/mls_group/
exporting.rs1use std::{
2 fmt::{Debug, Formatter},
3 marker::PhantomData,
4};
5
6use errors::{ExportGroupInfoError, ExportSecretError};
7use openmls_traits::{crypto::OpenMlsCrypto, signatures::Signer};
8use zeroize::ZeroizeOnDrop;
9
10use crate::{
11 ciphersuite::{HpkePublicKey, Secret},
12 extensions::errors::InvalidExtensionError,
13 schedule::{EpochAuthenticator, ResumptionPskSecret},
14};
15#[cfg(feature = "extensions-draft")]
16use crate::{
17 component::ComponentId,
18 group::{PendingSafeExportSecretError, SafeExportSecretError},
19};
20
21use super::*;
22
23pub struct ExportedSecret<T> {
28 secret: Secret,
29 _marker: PhantomData<T>,
30}
31
32pub struct GroupExport;
34
35pub struct StagedCommitExport;
37
38pub struct StagedWelcomeExport;
40
41pub struct ProcessedWelcomeExport;
43
44#[cfg(feature = "extensions-draft")]
46pub struct GroupSafeExport;
47
48#[cfg(feature = "extensions-draft")]
51pub struct PendingSafeExport;
52
53#[cfg(feature = "extensions-draft")]
58pub struct StagedCommitSafeExport;
59
60impl<T> ExportedSecret<T> {
61 pub(crate) fn new(secret: Secret) -> Self {
62 Self {
63 secret,
64 _marker: PhantomData,
65 }
66 }
67
68 pub fn as_slice(&self) -> &[u8] {
70 self.secret.as_slice()
71 }
72}
73
74#[cfg(feature = "extensions-draft")]
75impl ExportedSecret<StagedCommitSafeExport> {
76 pub(crate) fn into_pending_safe_export(self) -> ExportedSecret<PendingSafeExport> {
79 ExportedSecret::new(self.secret)
80 }
81}
82
83impl<T> AsRef<[u8]> for ExportedSecret<T> {
84 fn as_ref(&self) -> &[u8] {
85 self.secret.as_slice()
86 }
87}
88
89impl<T> ZeroizeOnDrop for ExportedSecret<T> {}
91
92impl<T> Debug for ExportedSecret<T> {
93 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
94 f.debug_struct("ExportedSecret")
95 .field("secret", &self.secret)
96 .finish()
97 }
98}
99
100impl<T, U> PartialEq<ExportedSecret<U>> for ExportedSecret<T> {
101 fn eq(&self, other: &ExportedSecret<U>) -> bool {
103 self.secret == other.secret
104 }
105}
106
107impl MlsGroup {
108 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
116 &self,
117 crypto: &CryptoProvider,
118 label: &str,
119 context: &[u8],
120 key_length: usize,
121 ) -> Result<ExportedSecret<GroupExport>, ExportSecretError> {
122 if key_length > u16::MAX as usize {
123 log::error!("Got a key that is larger than u16::MAX");
124 return Err(ExportSecretError::KeyLengthTooLong);
125 }
126
127 if self.is_active() {
128 Ok(ExportedSecret::new(
129 self.group_epoch_secrets
130 .exporter_secret()
131 .derive_exported_secret(self.ciphersuite(), crypto, label, context, key_length)
132 .map_err(LibraryError::unexpected_crypto_error)?,
133 ))
134 } else {
135 Err(ExportSecretError::GroupStateError(
136 MlsGroupStateError::UseAfterEviction,
137 ))
138 }
139 }
140
141 #[cfg(feature = "extensions-draft")]
144 pub fn safe_export_secret<Crypto: OpenMlsCrypto, Storage: StorageProvider>(
145 &mut self,
146 crypto: &Crypto,
147 storage: &Storage,
148 component_id: ComponentId,
149 ) -> Result<ExportedSecret<GroupSafeExport>, SafeExportSecretError<Storage::Error>> {
150 if !self.is_active() {
151 return Err(SafeExportSecretError::GroupState(
152 MlsGroupStateError::UseAfterEviction,
153 ));
154 }
155 let group_id = self.public_group.group_id();
156 let ciphersuite = self.ciphersuite();
157 let Some(application_export_tree) = self.application_export_tree.as_mut() else {
158 return Err(SafeExportSecretError::Unsupported);
159 };
160 let component_secret =
161 application_export_tree.safe_export_secret(crypto, ciphersuite, component_id)?;
162 storage
163 .write_application_export_tree(group_id, application_export_tree)
164 .map_err(SafeExportSecretError::Storage)?;
165
166 Ok(ExportedSecret::new(component_secret))
167 }
168
169 #[cfg(feature = "extensions-draft")]
172 pub fn safe_export_secret_from_pending<Provider: StorageProvider>(
173 &mut self,
174 crypto: &impl OpenMlsCrypto,
175 storage: &Provider,
176 component_id: ComponentId,
177 ) -> Result<ExportedSecret<PendingSafeExport>, PendingSafeExportSecretError<Provider::Error>>
178 {
179 let group_id = self.group_id().clone();
180 let MlsGroupState::PendingCommit(ref mut group_state) = self.group_state else {
181 return Err(PendingSafeExportSecretError::NoPendingCommit);
182 };
183 let PendingCommitState::Member(ref mut staged_commit) = **group_state else {
184 return Err(PendingSafeExportSecretError::NotGroupMember);
185 };
186 let secret = staged_commit.safe_export_secret(crypto, component_id)?;
187 storage
188 .write_group_state(&group_id, &self.group_state)
189 .map_err(PendingSafeExportSecretError::Storage)?;
190 Ok(secret.into_pending_safe_export())
191 }
192
193 pub fn epoch_authenticator(&self) -> &EpochAuthenticator {
195 self.group_epoch_secrets().epoch_authenticator()
196 }
197
198 pub fn resumption_psk_secret(&self) -> &ResumptionPskSecret {
200 self.group_epoch_secrets().resumption_psk()
201 }
202
203 pub fn branch_info(&self) -> BranchInfo {
216 BranchInfo {
217 version: self.version(),
218 ciphersuite: self.ciphersuite(),
219 group_id: self.group_id().clone(),
220 epoch: self.epoch(),
221 resumption_psk_secret: self.resumption_psk_secret().clone(),
222 member_credentials: self.members().map(|m| m.credential).collect(),
223 }
224 }
225
226 pub fn get_past_resumption_psk(&self, epoch: GroupEpoch) -> Option<&ResumptionPskSecret> {
229 self.resumption_psk_store.get(epoch)
230 }
231
232 pub fn export_group_info<CryptoProvider: OpenMlsCrypto>(
234 &self,
235 crypto: &CryptoProvider,
236 signer: &impl Signer,
237 with_ratchet_tree: bool,
238 ) -> Result<MlsMessageOut, ExportGroupInfoError> {
239 self.export_group_info_with_additional_extensions(crypto, signer, with_ratchet_tree, None)
240 }
241
242 pub fn export_group_info_with_additional_extensions<CryptoProvider: OpenMlsCrypto>(
247 &self,
248 crypto: &CryptoProvider,
249 signer: &impl Signer,
250 with_ratchet_tree: bool,
251 additional_extensions: impl IntoIterator<Item = Extension>,
252 ) -> Result<MlsMessageOut, ExportGroupInfoError> {
253 let extensions = {
254 let ratchet_tree_extension = || {
255 Extension::RatchetTree(RatchetTreeExtension::new(
256 self.public_group().export_ratchet_tree(),
257 ))
258 };
259
260 let external_pub_extension = || -> Result<Extension, ExportGroupInfoError> {
261 let external_pub = self
262 .group_epoch_secrets()
263 .external_secret()
264 .derive_external_keypair(crypto, self.ciphersuite())
265 .map_err(LibraryError::unexpected_crypto_error)?
266 .public;
267 Ok(Extension::ExternalPub(ExternalPubExtension::new(
268 HpkePublicKey::from(external_pub),
269 )))
270 };
271
272 let mut extensions = if with_ratchet_tree {
273 vec![ratchet_tree_extension(), external_pub_extension()?]
274 } else {
275 vec![external_pub_extension()?]
276 };
277
278 extensions.extend(
279 additional_extensions
280 .into_iter()
281 .map(|extension| {
282 if extension.as_ratchet_tree_extension().is_ok()
283 || extension.as_external_pub_extension().is_ok()
284 {
285 Err(InvalidExtensionError::CannotAddDirectlyToGroupInfo)
286 } else {
287 Ok(extension)
288 }
289 })
290 .collect::<Result<Vec<_>, _>>()?,
291 );
292
293 Extensions::from_vec(extensions)?
294 };
295
296 let group_info_tbs = GroupInfoTBS::new(
298 self.context().clone(),
299 extensions,
300 self.message_secrets()
301 .confirmation_key()
302 .tag(
303 crypto,
304 self.ciphersuite(),
305 self.context().confirmed_transcript_hash(),
306 )
307 .map_err(LibraryError::unexpected_crypto_error)?,
308 self.own_leaf_index(),
309 )?;
310
311 let group_info = group_info_tbs
313 .sign(signer)
314 .map_err(|_| LibraryError::custom("Signing failed"))?;
315 Ok(group_info.into())
316 }
317}