Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for deleting exporters #227

Merged
merged 4 commits into from
Jan 3, 2025
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