Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mls-rs/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ pub enum MlsError {
InvalidGroupInfo,
#[cfg_attr(feature = "std", error("Invalid welcome message"))]
InvalidWelcomeMessage,
#[cfg_attr(feature = "std", error("Exporter deleted"))]
ExporterDeleted,
}

impl IntoAnyError for MlsError {
Expand Down
8 changes: 8 additions & 0 deletions mls-rs/src/group/key_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ impl KeySchedule {
}
}

pub fn delete_exporter(&mut self) {
self.exporter_secret = Default::default();
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub async fn derive_for_external<P: CipherSuiteProvider>(
&self,
Expand Down Expand Up @@ -234,6 +238,10 @@ impl KeySchedule {
len: usize,
cipher_suite: &P,
) -> Result<Zeroizing<Vec<u8>>, MlsError> {
if self.exporter_secret.is_empty() {
return Err(MlsError::ExporterDeleted);
}

let secret = kdf_derive_secret(cipher_suite, &self.exporter_secret, label).await?;

let context_hash = cipher_suite
Expand Down
29 changes: 29 additions & 0 deletions mls-rs/src/group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,11 @@ where
Ok(self.key_schedule.authentication_secret.clone().into())
}

/// Export a secret for use outside of MLS. Each epoch, label, context
/// combination has a unique and independent secret. Secrets for all
/// epochs, labels and contexts can be derived until either the epoch
/// changes, i.e. a commit is received (or own commit is applied), or
/// [Group::delete_exporter] is called.
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub async fn export_secret(
&self,
Expand All @@ -1451,6 +1456,15 @@ where
.map(Into::into)
}

/// Delete the exporter secret. Afterwards the state contains no information
/// about any secrets outputted by [Group::export_secret] (for the current or
/// past epochs). This means that after calling this function, [Group::export_secret]
/// can no longer be used and we get forward secrecy for all secrets derived using
/// [Group::export_secret].
pub fn delete_exporter(&mut self) {
self.key_schedule.delete_exporter();
}

/// Export the current epoch's ratchet tree in serialized format.
///
/// This function is used to provide the current group tree to new members
Expand Down Expand Up @@ -4412,4 +4426,19 @@ mod tests {

assert_eq!(restored.group_state(), group.group_state());
}

#[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
async fn delete_exporter() {
let mut group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;

group.export_secret(b"123", b"", 15).await.unwrap();

group.delete_exporter();
let res = group.export_secret(b"123", b"", 15).await;
assert_matches!(res, Err(MlsError::ExporterDeleted));

group.commit(vec![]).await.unwrap();
group.apply_pending_commit().await.unwrap();
group.export_secret(b"123", b"", 15).await.unwrap();
}
}
Loading