Skip to content

Commit

Permalink
Add APIs to decrypt group info from welcome message (without tree) an…
Browse files Browse the repository at this point in the history
…d validate group info (without joining) (#188)

Co-authored-by: Marta Mularczyk <[email protected]>
  • Loading branch information
mulmarta and Marta Mularczyk authored Sep 19, 2024
1 parent e1fba48 commit a7b0422
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 113 deletions.
2 changes: 1 addition & 1 deletion mls-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mls-rs"
version = "0.41.3"
version = "0.41.4"
edition = "2021"
description = "An implementation of Messaging Layer Security (RFC 9420)"
homepage = "https://github.com/awslabs/mls-rs"
Expand Down
117 changes: 113 additions & 4 deletions mls-rs/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ use crate::client_builder::{recreate_config, BaseConfig, ClientBuilder, MakeConf
use crate::client_config::ClientConfig;
use crate::group::framing::MlsMessage;

use crate::group::{cipher_suite_provider, validate_group_info_joiner, GroupInfo};
use crate::group::{
framing::MlsMessagePayload, snapshot::Snapshot, ExportedTree, Group, NewMemberInfo,
};
#[cfg(feature = "by_ref_proposal")]
use crate::group::{
framing::{Content, MlsMessagePayload, PublicMessage, Sender, WireFormat},
framing::{Content, PublicMessage, Sender, WireFormat},
message_signature::AuthenticatedContent,
proposal::{AddProposal, Proposal},
};
use crate::group::{snapshot::Snapshot, ExportedTree, Group, NewMemberInfo};
use crate::identity::SigningIdentity;
use crate::key_package::{KeyPackageGeneration, KeyPackageGenerator};
use crate::protocol_version::ProtocolVersion;
Expand All @@ -24,7 +27,7 @@ use mls_rs_core::crypto::{CryptoProvider, SignatureSecretKey};
use mls_rs_core::error::{AnyError, IntoAnyError};
use mls_rs_core::extension::{ExtensionError, ExtensionList, ExtensionType};
use mls_rs_core::group::{GroupStateStorage, ProposalType};
use mls_rs_core::identity::CredentialType;
use mls_rs_core::identity::{CredentialType, IdentityProvider};
use mls_rs_core::key_package::KeyPackageStorage;

use crate::group::external_commit::ExternalCommitBuilder;
Expand Down Expand Up @@ -546,6 +549,46 @@ where
.await
}

/// Decrypt GroupInfo encrypted in the Welcome message without actually joining
/// the group. The ratchet tree is not needed.
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub async fn examine_welcome_message(
&self,
welcome_message: &MlsMessage,
) -> Result<GroupInfo, MlsError> {
Group::decrypt_group_info(welcome_message, &self.config).await
}

/// Validate GroupInfo message. This does NOT validate the ratchet tree in case
/// it is provided in the extension. It validates the signature, identity of the
/// signer, identities of external senders and cipher suite.
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub async fn validate_group_info(
&self,
group_info_message: &MlsMessage,
signer: &SigningIdentity,
) -> Result<(), MlsError> {
let MlsMessagePayload::GroupInfo(group_info) = &group_info_message.payload else {
return Err(MlsError::UnexpectedMessageType);
};

let cs = cipher_suite_provider(
self.config.crypto_provider(),
group_info.group_context.cipher_suite,
)?;

let id = self.config.identity_provider();

validate_group_info_joiner(group_info_message.version, group_info, signer, &id, &cs)
.await?;

id.validate_member(signer, None, Some(&group_info.group_context.extensions))
.await
.map_err(|e| MlsError::IdentityProviderError(e.into_any_error()))?;

Ok(())
}

/// 0-RTT add to an existing [group](crate::group::Group)
///
/// External commits allow for immediate entry into a
Expand Down Expand Up @@ -650,7 +693,7 @@ where
.cipher_suite_provider(cipher_suite)
.ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;

crate::group::validate_group_info_joiner(
crate::group::validate_tree_and_info_joiner(
protocol_version,
group_info,
tree_data,
Expand Down Expand Up @@ -1050,4 +1093,70 @@ mod tests {
let bob = alice.to_builder().extension_type(34.into()).build();
assert_eq!(bob.config.supported_extensions(), [33, 34].map(Into::into));
}

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

let (bob, kp) =
test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;

let commit = alice
.commit_builder()
.add_member(kp)
.unwrap()
.build()
.await
.unwrap();

alice.apply_pending_commit().await.unwrap();

let mut group_info = bob
.examine_welcome_message(&commit.welcome_messages[0])
.await
.unwrap();

// signature is random so we won't compare it
group_info.signature = vec![];
group_info.ungrease();

let mut expected_group_info = alice
.group_info_message(commit.ratchet_tree.is_none())
.await
.unwrap()
.into_group_info()
.unwrap();

expected_group_info.signature = vec![];
expected_group_info.ungrease();

assert_eq!(expected_group_info, group_info);
}

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

let bob = test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob")
.await
.0;

let group_info = alice.group_info_message(false).await.unwrap();
let alice_signer = alice.current_member_signing_identity().unwrap().clone();

bob.validate_group_info(&group_info, &alice_signer)
.await
.unwrap();

let other_signer = get_test_signing_identity(TEST_CIPHER_SUITE, b"alice")
.await
.0;

let res = bob.validate_group_info(&group_info, &other_signer).await;
assert_matches!(res, Err(MlsError::InvalidSignature));
}
}
4 changes: 2 additions & 2 deletions mls-rs/src/external_client/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
snapshot::RawGroupState,
state::GroupState,
transcript_hash::InterimTranscriptHash,
validate_group_info_joiner, ContentType, ExportedTree, GroupContext, GroupInfo, Roster,
validate_tree_and_info_joiner, ContentType, ExportedTree, GroupContext, GroupInfo, Roster,
Welcome,
},
identity::SigningIdentity,
Expand Down Expand Up @@ -129,7 +129,7 @@ impl<C: ExternalClientConfig + Clone> ExternalGroup<C> {
group_info.group_context.cipher_suite,
)?;

let public_tree = validate_group_info_joiner(
let public_tree = validate_tree_and_info_joiner(
protocol_version,
&group_info,
tree_data,
Expand Down
4 changes: 4 additions & 0 deletions mls-rs/src/grease.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ impl GroupInfo {
pub fn grease<P: CipherSuiteProvider>(&mut self, cs: &P) -> Result<(), MlsError> {
grease_functions::grease_extensions(&mut self.extensions, cs).map(|_| ())
}

pub fn ungrease(&mut self) {
grease_functions::ungrease_extensions(&mut self.extensions)
}
}

impl NewMemberInfo {
Expand Down
4 changes: 2 additions & 2 deletions mls-rs/src/group/external_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::group::{
PreSharedKeyProposal, {JustPreSharedKeyID, PreSharedKeyID},
};

use super::{validate_group_info_joiner, ExportedTree};
use super::{validate_tree_and_info_joiner, ExportedTree};

/// A builder that aids with the construction of an external commit.
#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::ffi_type(opaque))]
Expand Down Expand Up @@ -163,7 +163,7 @@ impl<C: ClientConfig> ExternalCommitBuilder<C> {
.get_as::<ExternalPubExt>()?
.ok_or(MlsError::MissingExternalPubExtension)?;

let public_tree = validate_group_info_joiner(
let public_tree = validate_tree_and_info_joiner(
protocol_version,
&group_info,
self.tree_data,
Expand Down
Loading

0 comments on commit a7b0422

Please sign in to comment.