1use 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 group::reinit::ReInitInfo,
14 schedule::{EpochAuthenticator, ResumptionPskSecret},
15};
16#[cfg(feature = "extensions-draft")]
17use crate::{
18 component::ComponentId,
19 group::{PendingSafeExportSecretError, SafeExportSecretError},
20};
21
22use super::*;
23
24pub struct ExportedSecret<T> {
29 secret: Secret,
30 _marker: PhantomData<T>,
31}
32
33pub struct GroupExport;
35
36pub struct StagedCommitExport;
38
39pub struct StagedWelcomeExport;
41
42pub struct ProcessedWelcomeExport;
44
45#[cfg(feature = "extensions-draft")]
47pub struct GroupSafeExport;
48
49#[cfg(feature = "extensions-draft")]
52pub struct PendingSafeExport;
53
54#[cfg(feature = "extensions-draft")]
59pub struct StagedCommitSafeExport;
60
61impl<T> ExportedSecret<T> {
62 pub(crate) fn new(secret: Secret) -> Self {
63 Self {
64 secret,
65 _marker: PhantomData,
66 }
67 }
68
69 pub fn as_slice(&self) -> &[u8] {
71 self.secret.as_slice()
72 }
73}
74
75#[cfg(feature = "extensions-draft")]
76impl ExportedSecret<StagedCommitSafeExport> {
77 pub(crate) fn into_pending_safe_export(self) -> ExportedSecret<PendingSafeExport> {
80 ExportedSecret::new(self.secret)
81 }
82}
83
84impl<T> AsRef<[u8]> for ExportedSecret<T> {
85 fn as_ref(&self) -> &[u8] {
86 self.secret.as_slice()
87 }
88}
89
90impl<T> ZeroizeOnDrop for ExportedSecret<T> {}
92
93impl<T> Debug for ExportedSecret<T> {
94 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
95 f.debug_struct("ExportedSecret")
96 .field("secret", &self.secret)
97 .finish()
98 }
99}
100
101impl<T, U> PartialEq<ExportedSecret<U>> for ExportedSecret<T> {
102 fn eq(&self, other: &ExportedSecret<U>) -> bool {
104 self.secret == other.secret
105 }
106}
107
108impl MlsGroup {
109 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
117 &self,
118 crypto: &CryptoProvider,
119 label: &str,
120 context: &[u8],
121 key_length: usize,
122 ) -> Result<ExportedSecret<GroupExport>, ExportSecretError> {
123 if key_length > u16::MAX as usize {
124 log::error!("Got a key that is larger than u16::MAX");
125 return Err(ExportSecretError::KeyLengthTooLong);
126 }
127
128 if self.is_active() {
129 Ok(ExportedSecret::new(
130 self.group_epoch_secrets
131 .exporter_secret()
132 .derive_exported_secret(self.ciphersuite(), crypto, label, context, key_length)
133 .map_err(LibraryError::unexpected_crypto_error)?,
134 ))
135 } else {
136 Err(ExportSecretError::GroupStateError(
137 MlsGroupStateError::UseAfterEviction,
138 ))
139 }
140 }
141
142 #[cfg(feature = "extensions-draft")]
145 pub fn safe_export_secret<Crypto: OpenMlsCrypto, Storage: StorageProvider>(
146 &mut self,
147 crypto: &Crypto,
148 storage: &Storage,
149 component_id: ComponentId,
150 ) -> Result<ExportedSecret<GroupSafeExport>, SafeExportSecretError<Storage::Error>> {
151 if !self.is_active() {
152 return Err(SafeExportSecretError::GroupState(
153 MlsGroupStateError::UseAfterEviction,
154 ));
155 }
156 let group_id = self.public_group.group_id();
157 let ciphersuite = self.ciphersuite();
158 let Some(application_export_tree) = self.application_export_tree.as_mut() else {
159 return Err(SafeExportSecretError::Unsupported);
160 };
161 let component_secret =
162 application_export_tree.safe_export_secret(crypto, ciphersuite, component_id)?;
163 storage
164 .write_application_export_tree(group_id, application_export_tree)
165 .map_err(SafeExportSecretError::Storage)?;
166
167 Ok(ExportedSecret::new(component_secret))
168 }
169
170 #[cfg(feature = "extensions-draft")]
173 pub fn safe_export_secret_from_pending<Provider: StorageProvider>(
174 &mut self,
175 crypto: &impl OpenMlsCrypto,
176 storage: &Provider,
177 component_id: ComponentId,
178 ) -> Result<ExportedSecret<PendingSafeExport>, PendingSafeExportSecretError<Provider::Error>>
179 {
180 let group_id = self.group_id().clone();
181 let MlsGroupState::PendingCommit(ref mut group_state) = self.group_state else {
182 return Err(PendingSafeExportSecretError::NoPendingCommit);
183 };
184 let PendingCommitState::Member(ref mut staged_commit) = **group_state else {
185 return Err(PendingSafeExportSecretError::NotGroupMember);
186 };
187 let secret = staged_commit.safe_export_secret(crypto, component_id)?;
188 storage
189 .write_group_state(&group_id, &self.group_state)
190 .map_err(PendingSafeExportSecretError::Storage)?;
191 Ok(secret.into_pending_safe_export())
192 }
193
194 pub fn epoch_authenticator(&self) -> &EpochAuthenticator {
196 self.group_epoch_secrets().epoch_authenticator()
197 }
198
199 pub fn resumption_psk_secret(&self) -> &ResumptionPskSecret {
201 self.group_epoch_secrets().resumption_psk()
202 }
203
204 pub fn branch_info(&self) -> BranchInfo {
217 BranchInfo {
218 version: self.version(),
219 ciphersuite: self.ciphersuite(),
220 group_id: self.group_id().clone(),
221 epoch: self.epoch(),
222 resumption_psk_secret: self.resumption_psk_secret().clone(),
223 member_credentials: self.members().map(|m| m.credential).collect(),
224 }
225 }
226
227 pub fn reinit_info(&self, proposal: ReInitProposal) -> Option<ReInitInfo> {
245 if !matches!(self.group_state, MlsGroupState::Inactive) {
250 return None;
251 }
252
253 Some(ReInitInfo {
254 proposal,
255 old_group_id: self.group_id().clone(),
256 old_group_epoch: self.epoch(),
257 resumption_psk_secret: self.resumption_psk_secret().clone(),
258 member_credentials: self.members().map(|member| member.credential).collect(),
259 })
260 }
261
262 pub fn get_past_resumption_psk(&self, epoch: GroupEpoch) -> Option<&ResumptionPskSecret> {
265 self.resumption_psk_store.get(epoch)
266 }
267
268 pub fn export_group_info<CryptoProvider: OpenMlsCrypto>(
270 &self,
271 crypto: &CryptoProvider,
272 signer: &impl Signer,
273 with_ratchet_tree: bool,
274 ) -> Result<MlsMessageOut, ExportGroupInfoError> {
275 self.export_group_info_with_additional_extensions(crypto, signer, with_ratchet_tree, None)
276 }
277
278 pub fn export_group_info_with_additional_extensions<CryptoProvider: OpenMlsCrypto>(
283 &self,
284 crypto: &CryptoProvider,
285 signer: &impl Signer,
286 with_ratchet_tree: bool,
287 additional_extensions: impl IntoIterator<Item = Extension>,
288 ) -> Result<MlsMessageOut, ExportGroupInfoError> {
289 let extensions = {
290 let ratchet_tree_extension = || {
291 Extension::RatchetTree(RatchetTreeExtension::new(
292 self.public_group().export_ratchet_tree(),
293 ))
294 };
295
296 let external_pub_extension = || -> Result<Extension, ExportGroupInfoError> {
297 let external_pub = self
298 .group_epoch_secrets()
299 .external_secret()
300 .derive_external_keypair(crypto, self.ciphersuite())
301 .map_err(LibraryError::unexpected_crypto_error)?
302 .public;
303 Ok(Extension::ExternalPub(ExternalPubExtension::new(
304 HpkePublicKey::from(external_pub),
305 )))
306 };
307
308 let mut extensions = if with_ratchet_tree {
309 vec![ratchet_tree_extension(), external_pub_extension()?]
310 } else {
311 vec![external_pub_extension()?]
312 };
313
314 extensions.extend(
315 additional_extensions
316 .into_iter()
317 .map(|extension| {
318 if extension.as_ratchet_tree_extension().is_ok()
319 || extension.as_external_pub_extension().is_ok()
320 {
321 Err(InvalidExtensionError::CannotAddDirectlyToGroupInfo)
322 } else {
323 Ok(extension)
324 }
325 })
326 .collect::<Result<Vec<_>, _>>()?,
327 );
328
329 Extensions::from_vec(extensions)?
330 };
331
332 let group_info_tbs = GroupInfoTBS::new(
334 self.context().clone(),
335 extensions,
336 self.message_secrets()
337 .confirmation_key()
338 .tag(
339 crypto,
340 self.ciphersuite(),
341 self.context().confirmed_transcript_hash(),
342 )
343 .map_err(LibraryError::unexpected_crypto_error)?,
344 self.own_leaf_index(),
345 )?;
346
347 let group_info = group_info_tbs
349 .sign(signer)
350 .map_err(|_| LibraryError::custom("Signing failed"))?;
351 Ok(group_info.into())
352 }
353}