From 4fdd47dfbb12f079ac35fe4edd3df4d772907229 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 16 May 2024 13:33:12 -0700 Subject: [PATCH 01/20] bring in update context proposal work for mutable metadata --- openmls/src/group/core_group/mod.rs | 4 +- .../src/group/core_group/test_proposals.rs | 2 +- openmls/src/group/errors.rs | 20 +- openmls/src/group/mls_group/errors.rs | 2 +- openmls/src/group/mls_group/proposal.rs | 55 ++++- openmls/src/group/mls_group/test_mls_group.rs | 210 ++++++++++++++++++ openmls/src/messages/proposals.rs | 2 +- 7 files changed, 286 insertions(+), 9 deletions(-) diff --git a/openmls/src/group/core_group/mod.rs b/openmls/src/group/core_group/mod.rs index a78fff0fc6..98308bfa9d 100644 --- a/openmls/src/group/core_group/mod.rs +++ b/openmls/src/group/core_group/mod.rs @@ -1135,12 +1135,12 @@ impl CoreGroup { } /// Create a new group context extension proposal - pub(crate) fn create_group_context_ext_proposal( + pub(crate) fn create_group_context_ext_proposal( &self, framing_parameters: FramingParameters, extensions: Extensions, signer: &impl Signer, - ) -> Result { + ) -> Result> { // Ensure that the group supports all the extensions that are wanted. let required_extension = extensions .iter() diff --git a/openmls/src/group/core_group/test_proposals.rs b/openmls/src/group/core_group/test_proposals.rs index ca166ea72a..cc092653e3 100644 --- a/openmls/src/group/core_group/test_proposals.rs +++ b/openmls/src/group/core_group/test_proposals.rs @@ -586,7 +586,7 @@ fn test_group_context_extension_proposal( &[CredentialType::Basic], )); let gce_proposal = alice_group - .create_group_context_ext_proposal( + .create_group_context_ext_proposal::( framing_parameters, Extensions::single(required_application_id), &alice_signer, diff --git a/openmls/src/group/errors.rs b/openmls/src/group/errors.rs index 37a9a72ad7..8dc1332e5e 100644 --- a/openmls/src/group/errors.rs +++ b/openmls/src/group/errors.rs @@ -500,7 +500,7 @@ pub(crate) enum CoreGroupParseMessageError { /// Create group context ext proposal error #[derive(Error, Debug, PartialEq, Clone)] -pub enum CreateGroupContextExtProposalError { +pub enum CreateGroupContextExtProposalError { /// See [`LibraryError`] for more details. #[error(transparent)] LibraryError(#[from] LibraryError), @@ -513,6 +513,15 @@ pub enum CreateGroupContextExtProposalError { /// See [`LeafNodeValidationError`] for more details. #[error(transparent)] LeafNodeValidation(#[from] LeafNodeValidationError), + /// See [`MlsGroupStateError`] for more details. + #[error(transparent)] + MlsGroupStateError(#[from] MlsGroupStateError), + /// See [`CreateCommitError`] for more details. + #[error(transparent)] + CreateCommitError(#[from] CreateCommitError), + /// Error writing updated group to storage. + #[error("Error writing updated group data to storage.")] + StorageError(StorageError), } /// Error merging a commit. @@ -537,6 +546,15 @@ pub enum GroupContextExtensionsProposalValidationError { #[error(transparent)] LibraryError(#[from] LibraryError), + /// The new extension types in required capabilties contails extensions that are not supported by all group members. + #[error( + "The new required capabilties contain extension types that are not supported by all group members." + )] + ExtensionNotSupportedByAllMembers, + /// Proposal changes the immutable metadata extension, which is not allowed. + #[error("Proposal changes the immutable metadata extension, which is not allowed.")] + ChangedImmutableMetadata, + /// The new extension types in required capabilties contails extensions that are not supported by all group members. #[error( "The new required capabilties contain extension types that are not supported by all group members." diff --git a/openmls/src/group/mls_group/errors.rs b/openmls/src/group/mls_group/errors.rs index b260b49447..abf94bdbec 100644 --- a/openmls/src/group/mls_group/errors.rs +++ b/openmls/src/group/mls_group/errors.rs @@ -340,7 +340,7 @@ pub enum ProposalError { ValidationError(#[from] ValidationError), /// See [`CreateGroupContextExtProposalError`] for more details. #[error(transparent)] - CreateGroupContextExtProposalError(#[from] CreateGroupContextExtProposalError), + CreateGroupContextExtProposalError(#[from] CreateGroupContextExtProposalError), /// Error writing proposal to storage. #[error("error writing proposal to storage")] StorageError(StorageError), diff --git a/openmls/src/group/mls_group/proposal.rs b/openmls/src/group/mls_group/proposal.rs index 19bfd78d5a..be6df8e930 100644 --- a/openmls/src/group/mls_group/proposal.rs +++ b/openmls/src/group/mls_group/proposal.rs @@ -1,8 +1,10 @@ use openmls_traits::{signatures::Signer, storage::StorageProvider, types::Ciphersuite}; use super::{ + core_group::create_commit_params::CreateCommitParams, errors::{ProposalError, ProposeAddMemberError, ProposeRemoveMemberError}, - CustomProposal, MlsGroup, + CreateGroupContextExtProposalError, CustomProposal, GroupContextExtensionProposal, MlsGroup, + MlsGroupState, PendingCommitState, Proposal, }; use crate::{ binary_tree::LeafNodeIndex, @@ -12,7 +14,7 @@ use crate::{ framing::MlsMessageOut, group::{errors::CreateAddProposalError, GroupId, QueuedProposal}, key_packages::KeyPackage, - messages::proposals::ProposalOrRefType, + messages::{group_info::GroupInfo, proposals::ProposalOrRefType}, prelude::LibraryError, schedule::PreSharedKeyId, storage::OpenMlsProvider, @@ -361,7 +363,7 @@ impl MlsGroup { ) -> Result<(MlsMessageOut, ProposalRef), ProposalError> { self.is_operational()?; - let proposal = self.group.create_group_context_ext_proposal( + let proposal = self.group.create_group_context_ext_proposal::( self.framing_parameters(), extensions, signer, @@ -384,4 +386,51 @@ impl MlsGroup { Ok((mls_message, proposal_ref)) } + + /// Updates group context extensions + /// + /// Returns an error when the group does not support all the required capabilities + /// in the new `extensions`. + #[allow(clippy::type_complexity)] + pub fn update_group_context_extensions( + &mut self, + provider: &Provider, + extensions: Extensions, + signer: &impl Signer, + ) -> Result< + (MlsMessageOut, Option, Option), + CreateGroupContextExtProposalError, + > { + self.is_operational()?; + + // Create group context extension proposals + let inline_proposals = vec![Proposal::GroupContextExtensions( + GroupContextExtensionProposal { extensions }, + )]; + + let params = CreateCommitParams::builder() + .framing_parameters(self.framing_parameters()) + .proposal_store(&self.proposal_store) + .inline_proposals(inline_proposals) + .build(); + let create_commit_result = self.group.create_commit(params, provider, signer)?; + + let mls_messages = self.content_to_mls_message(create_commit_result.commit, provider)?; + self.group_state = MlsGroupState::PendingCommit(Box::new(PendingCommitState::Member( + create_commit_result.staged_commit, + ))); + + provider + .storage() + .write_group_state(self.group_id(), &self.group_state) + .map_err(CreateGroupContextExtProposalError::StorageError)?; + + Ok(( + mls_messages, + create_commit_result + .welcome_option + .map(|w| MlsMessageOut::from_welcome(w, self.group.version())), + create_commit_result.group_info, + )) + } } diff --git a/openmls/src/group/mls_group/test_mls_group.rs b/openmls/src/group/mls_group/test_mls_group.rs index 25fc1958f9..2c2a60ec5d 100644 --- a/openmls/src/group/mls_group/test_mls_group.rs +++ b/openmls/src/group/mls_group/test_mls_group.rs @@ -1553,6 +1553,216 @@ fn update_group_context_with_unknown_extension() { + let alice_provider = Provider::default(); + let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) = + setup_client("Alice", ciphersuite, &alice_provider); + + // === Define the unknown group context extension and initial data === + const UNKNOWN_EXTENSION_TYPE: u16 = 0xff11; + let unknown_extension_data = vec![1, 2]; + let unknown_gc_extension = Extension::Unknown( + UNKNOWN_EXTENSION_TYPE, + UnknownExtension(unknown_extension_data), + ); + let required_extension_types = &[ExtensionType::Unknown(UNKNOWN_EXTENSION_TYPE)]; + let required_capabilities = Extension::RequiredCapabilities( + RequiredCapabilitiesExtension::new(required_extension_types, &[], &[]), + ); + let capabilities = Capabilities::new(None, None, Some(required_extension_types), None, None); + let test_gc_extensions = Extensions::from_vec(vec![ + unknown_gc_extension.clone(), + required_capabilities.clone(), + ]) + .expect("error creating test group context extensions"); + let mls_group_create_config = MlsGroupCreateConfig::builder() + .with_group_context_extensions(test_gc_extensions.clone()) + .expect("error adding unknown extension to config") + .capabilities(capabilities.clone()) + .ciphersuite(ciphersuite) + .build(); + + // === Alice creates a group === + let mut alice_group = MlsGroup::new( + &alice_provider, + &alice_signer, + &mls_group_create_config, + alice_credential_with_key, + ) + .expect("error creating group"); + + // === Verify the initial group context extension data is correct === + let group_context_extensions = alice_group.group().context().extensions(); + let mut extracted_data = None; + for extension in group_context_extensions.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data = Some(data.clone()); + } + } + assert_eq!( + extracted_data.unwrap(), + vec![1, 2], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); + + // === Alice adds Bob === + let bob_provider: Provider = Default::default(); + let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) = + setup_client("Bob", ciphersuite, &bob_provider); + + let bob_key_package = KeyPackage::builder() + .leaf_node_capabilities(capabilities) + .build( + ciphersuite, + &bob_provider, + &bob_signer, + bob_credential_with_key, + ) + .expect("error building key package"); + + let (_, welcome, _) = alice_group + .add_members( + &alice_provider, + &alice_signer, + &[bob_key_package.key_package().clone()], + ) + .unwrap(); + alice_group.merge_pending_commit(&alice_provider).unwrap(); + + let welcome: MlsMessageIn = welcome.into(); + let welcome = welcome + .into_welcome() + .expect("expected message to be a welcome"); + + let mut bob_group = StagedWelcome::new_from_welcome( + &bob_provider, + &MlsGroupJoinConfig::default(), + welcome, + Some(alice_group.export_ratchet_tree().into()), + ) + .expect("Error creating staged join from Welcome") + .into_group(&bob_provider) + .expect("Error creating group from staged join"); + + // === Verify Bob's initial group context extension data is correct === + let group_context_extensions = bob_group.group().context().extensions(); + let mut extracted_data_2 = None; + for extension in group_context_extensions.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data_2 = Some(data.clone()); + } + } + assert_eq!( + extracted_data_2.unwrap(), + vec![1, 2], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); + + // === Propose the new group context extension === + let updated_unknown_extension_data = vec![3, 4]; // Sample data for the extension + let updated_unknown_gc_extension = Extension::Unknown( + UNKNOWN_EXTENSION_TYPE, + UnknownExtension(updated_unknown_extension_data.clone()), + ); + + let mut updated_extensions = test_gc_extensions.clone(); + updated_extensions.add_or_replace(updated_unknown_gc_extension); + let (update_proposal, _) = alice_group + .propose_group_context_extensions(provider, updated_extensions, &alice_signer) + .expect("failed to propose group context extensions with unknown extension"); + + assert_eq!( + alice_group.pending_proposals().count(), + 1, + "Expected one pending proposal" + ); + + // === Commit to the proposed group context extension === + let (update_commit, _, _) = alice_group + .commit_to_pending_proposals(provider, &alice_signer) + .expect("failed to commit to pending group context extensions"); + + alice_group + .merge_pending_commit(provider) + .expect("error merging pending commit"); + + // === let bob process the updates === + assert_eq!( + bob_group.pending_proposals().count(), + 0, + "Expected no pending proposals" + ); + + let processed_update_message = bob_group + .process_message( + &bob_provider, + update_proposal.into_protocol_message().unwrap(), + ) + .expect("bob failed processing the update"); + + match processed_update_message.into_content() { + ProcessedMessageContent::ProposalMessage(msg) => { + bob_group + .store_pending_proposal(bob_provider.storage(), *msg) + .unwrap(); + } + other => panic!("expected proposal, got {other:?}"), + } + + assert_eq!( + bob_group.pending_proposals().count(), + 1, + "Expected one pending proposal" + ); + + let processed_commit_message = bob_group + .process_message( + &bob_provider, + update_commit.into_protocol_message().unwrap(), + ) + .expect("bob failed processing the update"); + + match processed_commit_message.into_content() { + ProcessedMessageContent::StagedCommitMessage(staged_commit) => bob_group + .merge_staged_commit(&bob_provider, *staged_commit) + .expect("error merging group context update commit"), + other => panic!("expected commit, got {other:?}"), + }; + + // === Verify the group context extension was updated === + let group_context_extensions = alice_group.group().context().extensions(); + let mut extracted_data_updated = None; + for extension in group_context_extensions.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data_updated = Some(data.clone()); + } + } + assert_eq!( + extracted_data_updated.unwrap(), + vec![3, 4], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); + + // === Verify Bob sees the group context extension updated === + let bob_group_loaded = MlsGroup::load(bob_provider.storage(), bob_group.group().group_id()) + .expect("error loading group") + .expect("no such group"); + let group_context_extensions_2 = bob_group_loaded.export_group_context().extensions(); + let mut extracted_data_2 = None; + for extension in group_context_extensions_2.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data_2 = Some(data.clone()); + } + } + assert_eq!( + extracted_data_2.unwrap(), + vec![3, 4], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); +} + // Test that unknown group context and leaf node extensions can be used in groups #[openmls_test] fn unknown_extensions() { diff --git a/openmls/src/messages/proposals.rs b/openmls/src/messages/proposals.rs index 181fa7cb9d..053f03c0dd 100644 --- a/openmls/src/messages/proposals.rs +++ b/openmls/src/messages/proposals.rs @@ -468,7 +468,7 @@ pub struct AppAckProposal { TlsSize, )] pub struct GroupContextExtensionProposal { - extensions: Extensions, + pub(crate) extensions: Extensions, } impl GroupContextExtensionProposal { From 2de4fe1b71ce1f28a8292a01a0f3e8364844cac6 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 16 May 2024 13:35:39 -0700 Subject: [PATCH 02/20] fix format issue --- openmls/src/group/core_group/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openmls/src/group/core_group/mod.rs b/openmls/src/group/core_group/mod.rs index 98308bfa9d..11f9f3deac 100644 --- a/openmls/src/group/core_group/mod.rs +++ b/openmls/src/group/core_group/mod.rs @@ -1140,7 +1140,8 @@ impl CoreGroup { framing_parameters: FramingParameters, extensions: Extensions, signer: &impl Signer, - ) -> Result> { + ) -> Result> + { // Ensure that the group supports all the extensions that are wanted. let required_extension = extensions .iter() From dd9c9ddf9e8d1a1b45a116eb3e48f1dc03e009cc Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Fri, 17 May 2024 14:16:47 +0200 Subject: [PATCH 03/20] Don't store encryption key separately This is unnecessary and was missed in #1565. --- openmls/src/key_packages/mod.rs | 5 ----- openmls/src/messages/tests/test_welcome.rs | 7 ++++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openmls/src/key_packages/mod.rs b/openmls/src/key_packages/mod.rs index 558b73efa9..17b8621de6 100644 --- a/openmls/src/key_packages/mod.rs +++ b/openmls/src/key_packages/mod.rs @@ -532,11 +532,6 @@ impl KeyPackageBuilder { .write_key_package(&full_kp.key_package.hash_ref(provider.crypto())?, &full_kp) .map_err(|_| KeyPackageNewError::StorageError)?; - // Store the encryption key pair in the key store. - encryption_keypair - .write(provider.storage()) - .map_err(|_| KeyPackageNewError::StorageError)?; - Ok(full_kp) } } diff --git a/openmls/src/messages/tests/test_welcome.rs b/openmls/src/messages/tests/test_welcome.rs index f817bbb073..76b73f9391 100644 --- a/openmls/src/messages/tests/test_welcome.rs +++ b/openmls/src/messages/tests/test_welcome.rs @@ -148,9 +148,10 @@ fn test_welcome_context_mismatch( welcome.encrypted_group_info = encrypted_verifiable_group_info.into(); // Create backup of encryption keypair, s.t. we can process the welcome a second time after failing. - let encryption_keypair = - EncryptionKeyPair::read(provider, bob_kpb.key_package().leaf_node().encryption_key()) - .unwrap(); + let encryption_keypair = EncryptionKeyPair::from(( + bob_kpb.key_package().leaf_node().encryption_key().clone(), + bob_kpb.private_encryption_key.clone(), + )); // Bob tries to join the group let err = StagedWelcome::new_from_welcome( From 7fbfd83ac6f595866538135178aa5fbfffe93989 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Fri, 17 May 2024 14:20:56 +0200 Subject: [PATCH 04/20] docs: not storing some values causes failures when loading the group --- traits/src/storage.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/traits/src/storage.rs b/traits/src/storage.rs index f0b138fe67..e5aad83e75 100644 --- a/traits/src/storage.rs +++ b/traits/src/storage.rs @@ -21,6 +21,10 @@ pub const V_TEST: u16 = u16::MAX; /// Many getters for lists return a `Result, E>`. In this case, if there was no error but /// the value doesn't exist, an empty vector should be returned. /// +/// Any value that uses the group id as key is required by the group. +/// Returning `None` or an error for any of them will cause a failure when +/// loading a group. +/// /// More details can be taken from the comments on the respective method. pub trait StorageProvider { /// An opaque error returned by all methods on this trait. @@ -349,6 +353,9 @@ pub trait StorageProvider { ) -> Result, Self::Error>; /// Returns the ResumptionPskStore for the group with the given id. + /// + /// Returning `None` here is considered an error because the store is needed + /// by OpenMLS when loading a group. fn resumption_psk_store< GroupId: traits::GroupId, ResumptionPskStore: traits::ResumptionPskStore, From d5905aafced47e9ea5439959fbe48bb5057c3c23 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Fri, 17 May 2024 14:52:38 +0200 Subject: [PATCH 05/20] benchmarks for ml-kem --- openmls/benches/benchmark.rs | 242 ++++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 4 deletions(-) diff --git a/openmls/benches/benchmark.rs b/openmls/benches/benchmark.rs index 0d11c40bd3..1516f846f1 100644 --- a/openmls/benches/benchmark.rs +++ b/openmls/benches/benchmark.rs @@ -6,11 +6,10 @@ extern crate rand; use criterion::Criterion; use openmls::prelude::*; use openmls_basic_credential::SignatureKeyPair; +use openmls_rust_crypto::OpenMlsRustCrypto; use openmls_traits::{crypto::OpenMlsCrypto, OpenMlsProvider}; -pub type OpenMlsRustCrypto = openmls_rust_crypto::OpenMlsRustCrypto; - -fn criterion_kp_bundle(c: &mut Criterion, provider: &impl OpenMlsProvider) { +fn criterion_key_package(c: &mut Criterion, provider: &impl OpenMlsProvider) { for &ciphersuite in provider.crypto().supported_ciphersuites().iter() { c.bench_function( &format!("KeyPackage create bundle with ciphersuite: {ciphersuite:?}"), @@ -38,14 +37,249 @@ fn criterion_kp_bundle(c: &mut Criterion, provider: &impl OpenMlsProvider) { } } +fn create_welcome(c: &mut Criterion, provider: &impl OpenMlsProvider) { + for &ciphersuite in provider.crypto().supported_ciphersuites().iter() { + c.bench_function( + &format!("Create a welcome message with ciphersuite: {ciphersuite:?}"), + move |b| { + b.iter_with_setup( + || { + let alice_credential = BasicCredential::new("Alice".into()); + let alice_signer = + SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap(); + let alice_credential_with_key = CredentialWithKey { + credential: alice_credential.into(), + signature_key: alice_signer.to_public_vec().into(), + }; + + let bob_credential = BasicCredential::new("Bob".into()); + let bob_signer = + SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap(); + let bob_credential_with_key = CredentialWithKey { + credential: bob_credential.into(), + signature_key: bob_signer.to_public_vec().into(), + }; + let bob_key_package = KeyPackage::builder() + .build( + ciphersuite, + provider, + &bob_signer, + bob_credential_with_key.clone(), + ) + .expect("An unexpected error occurred."); + + let mls_group_create_config = MlsGroupCreateConfig::builder() + .wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY) + .ciphersuite(ciphersuite) + .build(); + + // === Alice creates a group === + let alice_group = MlsGroup::new( + provider, + &alice_signer, + &mls_group_create_config, + alice_credential_with_key.clone(), + ) + .expect("An unexpected error occurred."); + + (alice_signer, alice_group, bob_key_package) + }, + |(alice_signer, mut alice_group, bob_key_package)| { + let _welcome = match alice_group.add_members( + provider, + &alice_signer, + &[bob_key_package.key_package().clone()], + ) { + Ok((_, welcome, _)) => welcome, + Err(e) => panic!("Could not add member to group: {e:?}"), + }; + }, + ); + }, + ); + } +} + +fn join_group(c: &mut Criterion, provider: &impl OpenMlsProvider) { + for &ciphersuite in provider.crypto().supported_ciphersuites().iter() { + c.bench_function( + &format!("Join a group with ciphersuite: {ciphersuite:?}"), + move |b| { + b.iter_with_setup( + || { + let alice_credential = BasicCredential::new("Alice".into()); + let alice_signer = + SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap(); + let alice_credential_with_key = CredentialWithKey { + credential: alice_credential.into(), + signature_key: alice_signer.to_public_vec().into(), + }; + + let bob_credential = BasicCredential::new("Bob".into()); + let bob_signer = + SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap(); + let bob_credential_with_key = CredentialWithKey { + credential: bob_credential.into(), + signature_key: bob_signer.to_public_vec().into(), + }; + let bob_key_package = KeyPackage::builder() + .build( + ciphersuite, + provider, + &bob_signer, + bob_credential_with_key.clone(), + ) + .expect("An unexpected error occurred."); + + let mls_group_create_config = MlsGroupCreateConfig::builder() + .wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY) + .ciphersuite(ciphersuite) + .build(); + + // === Alice creates a group === + let mut alice_group = MlsGroup::new( + provider, + &alice_signer, + &mls_group_create_config, + alice_credential_with_key.clone(), + ) + .expect("An unexpected error occurred."); + + let welcome = match alice_group.add_members( + provider, + &alice_signer, + &[bob_key_package.key_package().clone()], + ) { + Ok((_, welcome, _)) => welcome, + Err(e) => panic!("Could not add member to group: {e:?}"), + }; + + alice_group + .merge_pending_commit(provider) + .expect("error merging pending commit"); + + (alice_group, mls_group_create_config, welcome) + }, + |(alice_group, mls_group_create_config, welcome)| { + let welcome: MlsMessageIn = welcome.into(); + let welcome = welcome + .into_welcome() + .expect("expected the message to be a welcome message"); + let _bob_group = StagedWelcome::new_from_welcome( + provider, + mls_group_create_config.join_config(), + welcome, + Some(alice_group.export_ratchet_tree().into()), + ) + .unwrap() + .into_group(provider); + }, + ); + }, + ); + } +} + +fn create_commit(c: &mut Criterion, provider: &impl OpenMlsProvider) { + for &ciphersuite in provider.crypto().supported_ciphersuites().iter() { + c.bench_function( + &format!("Create a commit with ciphersuite: {ciphersuite:?}"), + move |b| { + b.iter_with_setup( + || { + let alice_credential = BasicCredential::new("Alice".into()); + let alice_signer = + SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap(); + let alice_credential_with_key = CredentialWithKey { + credential: alice_credential.into(), + signature_key: alice_signer.to_public_vec().into(), + }; + + let bob_credential = BasicCredential::new("Bob".into()); + let bob_signer = + SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap(); + let bob_credential_with_key = CredentialWithKey { + credential: bob_credential.into(), + signature_key: bob_signer.to_public_vec().into(), + }; + let bob_key_package = KeyPackage::builder() + .build( + ciphersuite, + provider, + &bob_signer, + bob_credential_with_key.clone(), + ) + .expect("An unexpected error occurred."); + + let mls_group_create_config = MlsGroupCreateConfig::builder() + .wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY) + .ciphersuite(ciphersuite) + .build(); + + // === Alice creates a group === + let mut alice_group = MlsGroup::new( + provider, + &alice_signer, + &mls_group_create_config, + alice_credential_with_key.clone(), + ) + .expect("An unexpected error occurred."); + + let welcome = match alice_group.add_members( + provider, + &alice_signer, + &[bob_key_package.key_package().clone()], + ) { + Ok((_, welcome, _)) => welcome, + Err(e) => panic!("Could not add member to group: {e:?}"), + }; + + alice_group + .merge_pending_commit(provider) + .expect("error merging pending commit"); + + let welcome: MlsMessageIn = welcome.into(); + let welcome = welcome + .into_welcome() + .expect("expected the message to be a welcome message"); + let bob_group = StagedWelcome::new_from_welcome( + provider, + mls_group_create_config.join_config(), + welcome, + Some(alice_group.export_ratchet_tree().into()), + ) + .unwrap() + .into_group(provider) + .unwrap(); + + (bob_group, bob_signer) + }, + |(mut bob_group, bob_signer)| { + let (queued_message, welcome_option, _group_info) = + bob_group.self_update(provider, &bob_signer).unwrap(); + + bob_group + .merge_pending_commit(provider) + .expect("error merging pending commit"); + }, + ); + }, + ); + } +} + fn kp_bundle_rust_crypto(c: &mut Criterion) { let provider = &OpenMlsRustCrypto::default(); println!("provider: RustCrypto"); - criterion_kp_bundle(c, provider); + criterion_key_package(c, provider); } fn criterion_benchmark(c: &mut Criterion) { kp_bundle_rust_crypto(c); + criterion_key_package(c, &openmls_libcrux_crypto::Provider::default()); + create_welcome(c, &openmls_libcrux_crypto::Provider::default()); + join_group(c, &openmls_libcrux_crypto::Provider::default()); + create_commit(c, &openmls_libcrux_crypto::Provider::default()); } criterion_group!(benches, criterion_benchmark); From 32f344ba3fe9d81f78616ebcd4b696f6737b4137 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 17 May 2024 16:05:48 -0700 Subject: [PATCH 06/20] dont make the crate public --- openmls/src/group/mls_group/proposal.rs | 2 +- openmls/src/group/mls_group/test_mls_group.rs | 210 ------------------ openmls/src/messages/proposals.rs | 2 +- 3 files changed, 2 insertions(+), 212 deletions(-) diff --git a/openmls/src/group/mls_group/proposal.rs b/openmls/src/group/mls_group/proposal.rs index be6df8e930..150017369c 100644 --- a/openmls/src/group/mls_group/proposal.rs +++ b/openmls/src/group/mls_group/proposal.rs @@ -405,7 +405,7 @@ impl MlsGroup { // Create group context extension proposals let inline_proposals = vec![Proposal::GroupContextExtensions( - GroupContextExtensionProposal { extensions }, + GroupContextExtensionProposal::new(extensions), )]; let params = CreateCommitParams::builder() diff --git a/openmls/src/group/mls_group/test_mls_group.rs b/openmls/src/group/mls_group/test_mls_group.rs index 2c2a60ec5d..25fc1958f9 100644 --- a/openmls/src/group/mls_group/test_mls_group.rs +++ b/openmls/src/group/mls_group/test_mls_group.rs @@ -1553,216 +1553,6 @@ fn update_group_context_with_unknown_extension() { - let alice_provider = Provider::default(); - let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) = - setup_client("Alice", ciphersuite, &alice_provider); - - // === Define the unknown group context extension and initial data === - const UNKNOWN_EXTENSION_TYPE: u16 = 0xff11; - let unknown_extension_data = vec![1, 2]; - let unknown_gc_extension = Extension::Unknown( - UNKNOWN_EXTENSION_TYPE, - UnknownExtension(unknown_extension_data), - ); - let required_extension_types = &[ExtensionType::Unknown(UNKNOWN_EXTENSION_TYPE)]; - let required_capabilities = Extension::RequiredCapabilities( - RequiredCapabilitiesExtension::new(required_extension_types, &[], &[]), - ); - let capabilities = Capabilities::new(None, None, Some(required_extension_types), None, None); - let test_gc_extensions = Extensions::from_vec(vec![ - unknown_gc_extension.clone(), - required_capabilities.clone(), - ]) - .expect("error creating test group context extensions"); - let mls_group_create_config = MlsGroupCreateConfig::builder() - .with_group_context_extensions(test_gc_extensions.clone()) - .expect("error adding unknown extension to config") - .capabilities(capabilities.clone()) - .ciphersuite(ciphersuite) - .build(); - - // === Alice creates a group === - let mut alice_group = MlsGroup::new( - &alice_provider, - &alice_signer, - &mls_group_create_config, - alice_credential_with_key, - ) - .expect("error creating group"); - - // === Verify the initial group context extension data is correct === - let group_context_extensions = alice_group.group().context().extensions(); - let mut extracted_data = None; - for extension in group_context_extensions.iter() { - if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { - extracted_data = Some(data.clone()); - } - } - assert_eq!( - extracted_data.unwrap(), - vec![1, 2], - "The data of Extension::Unknown(0xff11) does not match the expected data" - ); - - // === Alice adds Bob === - let bob_provider: Provider = Default::default(); - let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) = - setup_client("Bob", ciphersuite, &bob_provider); - - let bob_key_package = KeyPackage::builder() - .leaf_node_capabilities(capabilities) - .build( - ciphersuite, - &bob_provider, - &bob_signer, - bob_credential_with_key, - ) - .expect("error building key package"); - - let (_, welcome, _) = alice_group - .add_members( - &alice_provider, - &alice_signer, - &[bob_key_package.key_package().clone()], - ) - .unwrap(); - alice_group.merge_pending_commit(&alice_provider).unwrap(); - - let welcome: MlsMessageIn = welcome.into(); - let welcome = welcome - .into_welcome() - .expect("expected message to be a welcome"); - - let mut bob_group = StagedWelcome::new_from_welcome( - &bob_provider, - &MlsGroupJoinConfig::default(), - welcome, - Some(alice_group.export_ratchet_tree().into()), - ) - .expect("Error creating staged join from Welcome") - .into_group(&bob_provider) - .expect("Error creating group from staged join"); - - // === Verify Bob's initial group context extension data is correct === - let group_context_extensions = bob_group.group().context().extensions(); - let mut extracted_data_2 = None; - for extension in group_context_extensions.iter() { - if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { - extracted_data_2 = Some(data.clone()); - } - } - assert_eq!( - extracted_data_2.unwrap(), - vec![1, 2], - "The data of Extension::Unknown(0xff11) does not match the expected data" - ); - - // === Propose the new group context extension === - let updated_unknown_extension_data = vec![3, 4]; // Sample data for the extension - let updated_unknown_gc_extension = Extension::Unknown( - UNKNOWN_EXTENSION_TYPE, - UnknownExtension(updated_unknown_extension_data.clone()), - ); - - let mut updated_extensions = test_gc_extensions.clone(); - updated_extensions.add_or_replace(updated_unknown_gc_extension); - let (update_proposal, _) = alice_group - .propose_group_context_extensions(provider, updated_extensions, &alice_signer) - .expect("failed to propose group context extensions with unknown extension"); - - assert_eq!( - alice_group.pending_proposals().count(), - 1, - "Expected one pending proposal" - ); - - // === Commit to the proposed group context extension === - let (update_commit, _, _) = alice_group - .commit_to_pending_proposals(provider, &alice_signer) - .expect("failed to commit to pending group context extensions"); - - alice_group - .merge_pending_commit(provider) - .expect("error merging pending commit"); - - // === let bob process the updates === - assert_eq!( - bob_group.pending_proposals().count(), - 0, - "Expected no pending proposals" - ); - - let processed_update_message = bob_group - .process_message( - &bob_provider, - update_proposal.into_protocol_message().unwrap(), - ) - .expect("bob failed processing the update"); - - match processed_update_message.into_content() { - ProcessedMessageContent::ProposalMessage(msg) => { - bob_group - .store_pending_proposal(bob_provider.storage(), *msg) - .unwrap(); - } - other => panic!("expected proposal, got {other:?}"), - } - - assert_eq!( - bob_group.pending_proposals().count(), - 1, - "Expected one pending proposal" - ); - - let processed_commit_message = bob_group - .process_message( - &bob_provider, - update_commit.into_protocol_message().unwrap(), - ) - .expect("bob failed processing the update"); - - match processed_commit_message.into_content() { - ProcessedMessageContent::StagedCommitMessage(staged_commit) => bob_group - .merge_staged_commit(&bob_provider, *staged_commit) - .expect("error merging group context update commit"), - other => panic!("expected commit, got {other:?}"), - }; - - // === Verify the group context extension was updated === - let group_context_extensions = alice_group.group().context().extensions(); - let mut extracted_data_updated = None; - for extension in group_context_extensions.iter() { - if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { - extracted_data_updated = Some(data.clone()); - } - } - assert_eq!( - extracted_data_updated.unwrap(), - vec![3, 4], - "The data of Extension::Unknown(0xff11) does not match the expected data" - ); - - // === Verify Bob sees the group context extension updated === - let bob_group_loaded = MlsGroup::load(bob_provider.storage(), bob_group.group().group_id()) - .expect("error loading group") - .expect("no such group"); - let group_context_extensions_2 = bob_group_loaded.export_group_context().extensions(); - let mut extracted_data_2 = None; - for extension in group_context_extensions_2.iter() { - if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { - extracted_data_2 = Some(data.clone()); - } - } - assert_eq!( - extracted_data_2.unwrap(), - vec![3, 4], - "The data of Extension::Unknown(0xff11) does not match the expected data" - ); -} - // Test that unknown group context and leaf node extensions can be used in groups #[openmls_test] fn unknown_extensions() { diff --git a/openmls/src/messages/proposals.rs b/openmls/src/messages/proposals.rs index 053f03c0dd..181fa7cb9d 100644 --- a/openmls/src/messages/proposals.rs +++ b/openmls/src/messages/proposals.rs @@ -468,7 +468,7 @@ pub struct AppAckProposal { TlsSize, )] pub struct GroupContextExtensionProposal { - pub(crate) extensions: Extensions, + extensions: Extensions, } impl GroupContextExtensionProposal { From 5523a063e66ee2ee2245b5f210af88a8ac96fcd6 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Wed, 22 May 2024 08:50:23 +0200 Subject: [PATCH 07/20] check that staged commit has gce proposal applied --- openmls/tests/test_mls_group.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openmls/tests/test_mls_group.rs b/openmls/tests/test_mls_group.rs index f86cc90dbe..3f83faff72 100644 --- a/openmls/tests/test_mls_group.rs +++ b/openmls/tests/test_mls_group.rs @@ -1229,6 +1229,10 @@ fn group_context_extensions_proposal( // No required capabilities, so no specifically required extensions. assert!(alice_group.extensions().required_capabilities().is_none()); + // The old group context + let group_context_before = alice_group.export_group_context().clone(); + assert_eq!(group_context_before.extensions(), &Extensions::empty()); + let new_extensions = Extensions::single(Extension::RequiredCapabilities( RequiredCapabilitiesExtension::new(&[ExtensionType::RequiredCapabilities], &[], &[]), )); @@ -1247,6 +1251,14 @@ fn group_context_extensions_proposal( .commit_to_pending_proposals(provider, &alice_signer) .expect("failed to commit to pending proposals"); + // The staged commit has the new group context extensions. + let group_context_staged = alice_group + .pending_commit() + .unwrap() + .group_context() + .clone(); + assert_eq!(group_context_staged.extensions(), &new_extensions); + alice_group .merge_pending_commit(provider) .expect("error merging pending commit"); From 9ae58bc022cadbe65c836fa66e69d6ea17c6f465 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Wed, 22 May 2024 08:50:35 +0200 Subject: [PATCH 08/20] add logging to all tests --- openmls_test/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openmls_test/src/lib.rs b/openmls_test/src/lib.rs index 8908ec7500..8ae2cd39af 100644 --- a/openmls_test/src/lib.rs +++ b/openmls_test/src/lib.rs @@ -32,6 +32,7 @@ pub fn openmls_test(_attr: TokenStream, item: TokenStream) -> TokenStream { use openmls_traits::{types::Ciphersuite, crypto::OpenMlsCrypto}; type Provider = OpenMlsRustCrypto; + let _ = pretty_env_logger::try_init(); let ciphersuite = Ciphersuite::try_from(#val).unwrap(); let provider = OpenMlsRustCrypto::default(); @@ -67,6 +68,7 @@ pub fn openmls_test(_attr: TokenStream, item: TokenStream) -> TokenStream { use openmls_traits::{types::Ciphersuite, prelude::*}; type Provider = OpenMlsLibcrux; + let _ = pretty_env_logger::try_init(); let ciphersuite = Ciphersuite::try_from(#val).unwrap(); let provider = OpenMlsLibcrux::default(); From 4952fcb24a2e9067eddbb8670d08ccbc5ce79a2c Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Wed, 22 May 2024 12:04:56 +0200 Subject: [PATCH 09/20] add changelog for new storage API --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c68edd5b8..f939c6bd31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1506](https://github.com/openmls/openmls/pull/1506): Add `StagedWelcome` and `StagedCoreWelcome` to make joining a group staged in order to inspect the `Welcome` message. This was followed up with PR [#1533](https://github.com/openmls/openmls/pull/1533) to adjust the API. - [#1516](https://github.com/openmls/openmls/pull/1516): Add `MlsGroup::clear_pending_proposals` to the public API; this allows users to clear a group's internal `ProposalStore` +- [#1565](https://github.com/openmls/openmls/pull/1565): Add new `StorageProvider` trait to the `openmls_traits` crate. ### Changed @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1548](https://github.com/openmls/openmls/pull/1548): CryptoConfig is now replaced by just Ciphersuite. - [#1542](https://github.com/openmls/openmls/pull/1542): Add support for custom proposals. ProposalType::Unknown is now called ProposalType::Other. Proposal::Unknown is now called Proposal::Other. - [#1559](https://github.com/openmls/openmls/pull/1559): Remove the `PartialEq` type constraint on the error type of both the `OpenMlsRand` and `OpenMlsKeyStore` traits. Additionally, remove the `Clone` type constraint on the error type of the `OpenMlsRand` trait. +- [#1565](https://github.com/openmls/openmls/pull/1565): Removed `OpenMlsKeyStore` and replace it with a new `StorageProvider` trait in the `openmls_traits` crate. ### Fixed From 2e587c15876b86b307ae33a1ce4b8d8297dc97cd Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Thu, 23 May 2024 16:23:09 -0700 Subject: [PATCH 10/20] adds comment description for update_group_context_extensions --- openmls/src/group/mls_group/proposal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmls/src/group/mls_group/proposal.rs b/openmls/src/group/mls_group/proposal.rs index 150017369c..bb204602fa 100644 --- a/openmls/src/group/mls_group/proposal.rs +++ b/openmls/src/group/mls_group/proposal.rs @@ -387,7 +387,9 @@ impl MlsGroup { Ok((mls_message, proposal_ref)) } - /// Updates group context extensions + /// Updates Group Context Extensions + /// + /// Commits to the Group Context Extension inline proposal using the [`Extensions`] /// /// Returns an error when the group does not support all the required capabilities /// in the new `extensions`. From 9d2f01870ac198422d542019b12021cd6e9c7cf4 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Tue, 4 Jun 2024 10:52:14 +0200 Subject: [PATCH 11/20] API for processed welcome Fixes #1585 --- openmls/src/group/core_group/mod.rs | 2 +- .../src/group/core_group/new_from_welcome.rs | 421 ++++++++++-------- openmls/src/group/mls_group/creation.rs | 131 ++++-- openmls/src/group/mls_group/mod.rs | 22 +- openmls/src/messages/group_info.rs | 4 +- openmls/src/messages/tests/test_welcome.rs | 80 +++- 6 files changed, 432 insertions(+), 228 deletions(-) diff --git a/openmls/src/group/core_group/mod.rs b/openmls/src/group/core_group/mod.rs index a78fff0fc6..4004ba822a 100644 --- a/openmls/src/group/core_group/mod.rs +++ b/openmls/src/group/core_group/mod.rs @@ -6,7 +6,7 @@ //! error, will still return a `Result` since they may throw a `LibraryError`. // Private -mod new_from_welcome; +pub(super) mod new_from_welcome; // Crate pub(crate) mod create_commit_params; diff --git a/openmls/src/group/core_group/new_from_welcome.rs b/openmls/src/group/core_group/new_from_welcome.rs index 0bb4a7c123..345faa00b7 100644 --- a/openmls/src/group/core_group/new_from_welcome.rs +++ b/openmls/src/group/core_group/new_from_welcome.rs @@ -19,207 +19,26 @@ impl StagedCoreWelcome { ratchet_tree: Option, key_package_bundle: KeyPackageBundle, provider: &Provider, - mut resumption_psk_store: ResumptionPskStore, + resumption_psk_store: ResumptionPskStore, ) -> Result> { log::debug!("CoreGroup::new_from_welcome_internal"); - let ciphersuite = welcome.ciphersuite(); - - // Find key_package in welcome secrets - let egs = if let Some(egs) = CoreGroup::find_key_package_from_welcome_secrets( - key_package_bundle - .key_package() - .hash_ref(provider.crypto())?, - welcome.secrets(), - ) { - egs - } else { - return Err(WelcomeError::JoinerSecretNotFound); - }; - if ciphersuite != key_package_bundle.key_package().ciphersuite() { - let e = WelcomeError::CiphersuiteMismatch; - debug!("new_from_welcome {:?}", e); - return Err(e); - } - - let group_secrets = GroupSecrets::try_from_ciphertext( - key_package_bundle.init_private_key(), - egs.encrypted_group_secrets(), - welcome.encrypted_group_info(), - ciphersuite, - provider.crypto(), - )?; - - // Prepare the PskSecret - let psk_secret = { - let psks = load_psks( - provider.storage(), - &resumption_psk_store, - &group_secrets.psks, - )?; - - PskSecret::new(provider.crypto(), ciphersuite, psks)? - }; - - // Create key schedule - let mut key_schedule = KeySchedule::init( - ciphersuite, - provider.crypto(), - &group_secrets.joiner_secret, - psk_secret, - )?; - - // Derive welcome key & nonce from the key schedule - let (welcome_key, welcome_nonce) = key_schedule - .welcome(provider.crypto(), ciphersuite) - .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))? - .derive_welcome_key_nonce(provider.crypto(), ciphersuite) - .map_err(LibraryError::unexpected_crypto_error)?; - - let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext( - &welcome_key, - &welcome_nonce, - welcome.encrypted_group_info(), - &[], - provider.crypto(), - )?; - - // Make sure that we can support the required capabilities in the group info. - if let Some(required_capabilities) = - verifiable_group_info.extensions().required_capabilities() - { - // Also check that our key package actually supports the extensions. - // Per spec the sender must have checked this. But you never know. - key_package_bundle - .key_package() - .leaf_node() - .capabilities() - .supports_required_capabilities(required_capabilities)?; - } - - // Build the ratchet tree - - // Set nodes either from the extension or from the `nodes_option`. - // If we got a ratchet tree extension in the welcome, we enable it for - // this group. Note that this is not strictly necessary. But there's - // currently no other mechanism to enable the extension. - let (ratchet_tree, enable_ratchet_tree_extension) = - match verifiable_group_info.extensions().ratchet_tree() { - Some(extension) => (extension.ratchet_tree().clone(), true), - None => match ratchet_tree { - Some(ratchet_tree) => (ratchet_tree, false), - None => return Err(WelcomeError::MissingRatchetTree), - }, - }; - - // Since there is currently only the external pub extension, there is no - // group info extension of interest here. - let (public_group, _group_info_extensions) = PublicGroup::from_external( + let (ciphersuite, group_secrets, key_schedule, verifiable_group_info) = process_welcome( + welcome, + &key_package_bundle, provider, - ratchet_tree, - verifiable_group_info.clone(), - ProposalStore::new(), + &resumption_psk_store, )?; - // Find our own leaf in the tree. - let own_leaf_index = public_group - .members() - .find_map(|m| { - if m.signature_key - == key_package_bundle - .key_package() - .leaf_node() - .signature_key() - .as_slice() - { - Some(m.index) - } else { - None - } - }) - .ok_or(WelcomeError::PublicTreeError( - PublicTreeError::MalformedTree, - ))?; - - let (group_epoch_secrets, message_secrets) = { - let serialized_group_context = public_group - .group_context() - .tls_serialize_detached() - .map_err(LibraryError::missing_bound_check)?; - - // TODO #751: Implement PSK - key_schedule - .add_context(provider.crypto(), &serialized_group_context) - .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?; - - let epoch_secrets = key_schedule - .epoch_secrets(provider.crypto(), ciphersuite) - .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?; - - epoch_secrets.split_secrets( - serialized_group_context, - public_group.tree_size(), - own_leaf_index, - ) - }; - - let confirmation_tag = message_secrets - .confirmation_key() - .tag( - provider.crypto(), - ciphersuite, - public_group.group_context().confirmed_transcript_hash(), - ) - .map_err(LibraryError::unexpected_crypto_error)?; - - // Verify confirmation tag - if &confirmation_tag != public_group.confirmation_tag() { - log::error!("Confirmation tag mismatch"); - log_crypto!(trace, " Got: {:x?}", confirmation_tag); - log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag()); - debug_assert!(false, "Confirmation tag mismatch"); - return Err(WelcomeError::ConfirmationTagMismatch); - } - - let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets); - - // Extract and store the resumption PSK for the current epoch. - let resumption_psk = group_epoch_secrets.resumption_psk(); - resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone()); - - let welcome_sender_index = verifiable_group_info.signer(); - let path_keypairs = if let Some(path_secret) = group_secrets.path_secret { - let (path_keypairs, _commit_secret) = public_group - .derive_path_secrets( - provider.crypto(), - ciphersuite, - path_secret, - welcome_sender_index, - own_leaf_index, - ) - .map_err(|e| match e { - DerivePathError::LibraryError(e) => e.into(), - DerivePathError::PublicKeyMismatch => { - WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch) - } - })?; - Some(path_keypairs) - } else { - None - }; - - let group = StagedCoreWelcome { - public_group, - group_epoch_secrets, - own_leaf_index, - use_ratchet_tree_extension: enable_ratchet_tree_extension, - message_secrets_store, - resumption_psk_store, + build_staged_welcome( verifiable_group_info, + ratchet_tree, + provider, key_package_bundle, - path_keypairs, - }; - - Ok(group) + key_schedule, + ciphersuite, + resumption_psk_store, + group_secrets, + ) } /// Returns the [`LeafNodeIndex`] of the group member that authored the [`Welcome`] message. @@ -272,6 +91,220 @@ impl StagedCoreWelcome { } } +pub(in crate::group) fn build_staged_welcome( + verifiable_group_info: VerifiableGroupInfo, + ratchet_tree: Option, + provider: &Provider, + key_package_bundle: KeyPackageBundle, + mut key_schedule: KeySchedule, + ciphersuite: Ciphersuite, + mut resumption_psk_store: ResumptionPskStore, + group_secrets: GroupSecrets, +) -> Result> { + // Build the ratchet tree and group + + // Set nodes either from the extension or from the `nodes_option`. + // If we got a ratchet tree extension in the welcome, we enable it for + // this group. Note that this is not strictly necessary. But there's + // currently no other mechanism to enable the extension. + let (ratchet_tree, enable_ratchet_tree_extension) = + match verifiable_group_info.extensions().ratchet_tree() { + Some(extension) => (extension.ratchet_tree().clone(), true), + None => match ratchet_tree { + Some(ratchet_tree) => (ratchet_tree, false), + None => return Err(WelcomeError::MissingRatchetTree), + }, + }; + + // Since there is currently only the external pub extension, there is no + // group info extension of interest here. + let (public_group, _group_info_extensions) = PublicGroup::from_external( + provider, + ratchet_tree, + verifiable_group_info.clone(), + ProposalStore::new(), + )?; + + // Find our own leaf in the tree. + let own_leaf_index = public_group + .members() + .find_map(|m| { + if m.signature_key + == key_package_bundle + .key_package() + .leaf_node() + .signature_key() + .as_slice() + { + Some(m.index) + } else { + None + } + }) + .ok_or(WelcomeError::PublicTreeError( + PublicTreeError::MalformedTree, + ))?; + + let (group_epoch_secrets, message_secrets) = { + let serialized_group_context = public_group + .group_context() + .tls_serialize_detached() + .map_err(LibraryError::missing_bound_check)?; + + // TODO #751: Implement PSK + key_schedule + .add_context(provider.crypto(), &serialized_group_context) + .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?; + + let epoch_secrets = key_schedule + .epoch_secrets(provider.crypto(), ciphersuite) + .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?; + + epoch_secrets.split_secrets( + serialized_group_context, + public_group.tree_size(), + own_leaf_index, + ) + }; + + let confirmation_tag = message_secrets + .confirmation_key() + .tag( + provider.crypto(), + ciphersuite, + public_group.group_context().confirmed_transcript_hash(), + ) + .map_err(LibraryError::unexpected_crypto_error)?; + + // Verify confirmation tag + if &confirmation_tag != public_group.confirmation_tag() { + log::error!("Confirmation tag mismatch"); + log_crypto!(trace, " Got: {:x?}", confirmation_tag); + log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag()); + debug_assert!(false, "Confirmation tag mismatch"); + return Err(WelcomeError::ConfirmationTagMismatch); + } + + let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets); + + // Extract and store the resumption PSK for the current epoch. + let resumption_psk = group_epoch_secrets.resumption_psk(); + resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone()); + + let welcome_sender_index = verifiable_group_info.signer(); + let path_keypairs = if let Some(path_secret) = group_secrets.path_secret { + let (path_keypairs, _commit_secret) = public_group + .derive_path_secrets( + provider.crypto(), + ciphersuite, + path_secret, + welcome_sender_index, + own_leaf_index, + ) + .map_err(|e| match e { + DerivePathError::LibraryError(e) => e.into(), + DerivePathError::PublicKeyMismatch => { + WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch) + } + })?; + Some(path_keypairs) + } else { + None + }; + + let group = StagedCoreWelcome { + public_group, + group_epoch_secrets, + own_leaf_index, + use_ratchet_tree_extension: enable_ratchet_tree_extension, + message_secrets_store, + resumption_psk_store, + verifiable_group_info, + key_package_bundle, + path_keypairs, + }; + + Ok(group) +} + +/// Process a Welcome message up to the point where the ratchet tree is is required. +pub(in crate::group) fn process_welcome( + welcome: Welcome, + key_package_bundle: &KeyPackageBundle, + provider: &Provider, + resumption_psk_store: &ResumptionPskStore, +) -> Result< + (Ciphersuite, GroupSecrets, KeySchedule, VerifiableGroupInfo), + WelcomeError, +> { + let ciphersuite = welcome.ciphersuite(); + let egs = if let Some(egs) = CoreGroup::find_key_package_from_welcome_secrets( + key_package_bundle + .key_package() + .hash_ref(provider.crypto())?, + welcome.secrets(), + ) { + egs + } else { + return Err(WelcomeError::JoinerSecretNotFound); + }; + if ciphersuite != key_package_bundle.key_package().ciphersuite() { + let e = WelcomeError::CiphersuiteMismatch; + debug!("new_from_welcome {:?}", e); + return Err(e); + } + let group_secrets = GroupSecrets::try_from_ciphertext( + key_package_bundle.init_private_key(), + egs.encrypted_group_secrets(), + welcome.encrypted_group_info(), + ciphersuite, + provider.crypto(), + )?; + let psk_secret = { + let psks = load_psks( + provider.storage(), + resumption_psk_store, + &group_secrets.psks, + )?; + + PskSecret::new(provider.crypto(), ciphersuite, psks)? + }; + let key_schedule = KeySchedule::init( + ciphersuite, + provider.crypto(), + &group_secrets.joiner_secret, + psk_secret, + )?; + let (welcome_key, welcome_nonce) = key_schedule + .welcome(provider.crypto(), ciphersuite) + .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))? + .derive_welcome_key_nonce(provider.crypto(), ciphersuite) + .map_err(LibraryError::unexpected_crypto_error)?; + let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext( + &welcome_key, + &welcome_nonce, + welcome.encrypted_group_info(), + &[], + provider.crypto(), + )?; + if let Some(required_capabilities) = verifiable_group_info.extensions().required_capabilities() + { + // Also check that our key package actually supports the extensions. + // Per spec the sender must have checked this. But you never know. + key_package_bundle + .key_package() + .leaf_node() + .capabilities() + .supports_required_capabilities(required_capabilities)?; + } + Ok(( + ciphersuite, + group_secrets, + key_schedule, + verifiable_group_info, + )) +} + impl CoreGroup { // Helper functions diff --git a/openmls/src/group/mls_group/creation.rs b/openmls/src/group/mls_group/creation.rs index ff0522bb86..d15caae04c 100644 --- a/openmls/src/group/mls_group/creation.rs +++ b/openmls/src/group/mls_group/creation.rs @@ -147,6 +147,74 @@ fn transpose_err_opt(v: Result, E>) -> Option> { } } +impl ProcessedWelcome { + /// Creates a new processed [`Welcome`] message that can be used to parse + /// it before creating a [`StagedWelcome`]. + /// + /// This does not require a ratchet tree yet. + /// + /// [`Welcome`]: crate::messages::Welcome + pub fn new_from_welcome( + provider: &Provider, + mls_group_config: &MlsGroupJoinConfig, + welcome: Welcome, + ) -> Result> { + let (resumption_psk_store, key_package_bundle) = + keys_for_welcome(mls_group_config, &welcome, provider)?; + + let (ciphersuite, group_secrets, key_schedule, verifiable_group_info) = + crate::group::core_group::new_from_welcome::process_welcome( + welcome, + &key_package_bundle, + provider, + &resumption_psk_store, + )?; + + Ok(Self { + mls_group_config: mls_group_config.clone(), + ciphersuite, + group_secrets, + key_schedule, + verifiable_group_info, + resumption_psk_store, + key_package_bundle, + }) + } + + /// Get a reference to the GroupInfo in this Welcome message. + /// + /// **NOTE:** The group info contains **unverified** values. Use with caution. + pub fn unverified_group_info(&self) -> &VerifiableGroupInfo { + &self.verifiable_group_info + } + + /// Consume the `ProcessedWelcome` and combine it witht he ratchet tree into + /// a `StagedWelcome`. + pub fn into_staged_welcome( + self, + provider: &Provider, + ratchet_tree: Option, + ) -> Result> { + let group = crate::group::core_group::new_from_welcome::build_staged_welcome( + self.verifiable_group_info, + ratchet_tree, + provider, + self.key_package_bundle, + self.key_schedule, + self.ciphersuite, + self.resumption_psk_store, + self.group_secrets, + )?; + + let staged_welcome = StagedWelcome { + mls_group_config: self.mls_group_config, + group, + }; + + Ok(staged_welcome) + } +} + impl StagedWelcome { /// Creates a new staged welcome from a [`Welcome`] message. Returns an error /// ([`WelcomeError::NoMatchingKeyPackage`]) if no [`KeyPackage`] @@ -161,33 +229,8 @@ impl StagedWelcome { welcome: Welcome, ratchet_tree: Option, ) -> Result> { - let resumption_psk_store = - ResumptionPskStore::new(mls_group_config.number_of_resumption_psks); - let key_package_bundle: KeyPackageBundle = welcome - .secrets() - .iter() - .find_map(|egs| { - let hash_ref = egs.new_member(); - - transpose_err_opt( - provider - .storage() - .key_package(&hash_ref) - .map_err(WelcomeError::StorageError), - ) - }) - .ok_or(WelcomeError::NoMatchingKeyPackage)??; - - // Delete the [`KeyPackage`] and the corresponding private key from the - // key store, but only if it doesn't have a last resort extension. - if !key_package_bundle.key_package().last_resort() { - provider - .storage() - .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?) - .map_err(WelcomeError::StorageError)?; - } else { - log::debug!("Key package has last resort extension, not deleting"); - } + let (resumption_psk_store, key_package_bundle) = + keys_for_welcome(mls_group_config, &welcome, provider)?; let group = StagedCoreWelcome::new_from_welcome( welcome, @@ -248,3 +291,37 @@ impl StagedWelcome { Ok(mls_group) } } + +fn keys_for_welcome( + mls_group_config: &MlsGroupJoinConfig, + welcome: &Welcome, + provider: &Provider, +) -> Result< + (ResumptionPskStore, KeyPackageBundle), + WelcomeError<::StorageError>, +> { + let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks); + let key_package_bundle: KeyPackageBundle = welcome + .secrets() + .iter() + .find_map(|egs| { + let hash_ref = egs.new_member(); + + transpose_err_opt( + provider + .storage() + .key_package(&hash_ref) + .map_err(WelcomeError::StorageError), + ) + }) + .ok_or(WelcomeError::NoMatchingKeyPackage)??; + if !key_package_bundle.key_package().last_resort() { + provider + .storage() + .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?) + .map_err(WelcomeError::StorageError)?; + } else { + log::debug!("Key package has last resort extension, not deleting"); + } + Ok((resumption_psk_store, key_package_bundle)) +} diff --git a/openmls/src/group/mls_group/mod.rs b/openmls/src/group/mls_group/mod.rs index 501dc9c0d3..b32b229bf6 100644 --- a/openmls/src/group/mls_group/mod.rs +++ b/openmls/src/group/mls_group/mod.rs @@ -11,7 +11,7 @@ use crate::{ framing::{mls_auth_content::AuthenticatedContent, *}, group::*, key_packages::{KeyPackage, KeyPackageBundle}, - messages::proposals::*, + messages::{proposals::*, GroupSecrets}, schedule::ResumptionPskSecret, storage::{OpenMlsProvider, StorageProvider}, treesync::{node::leaf_node::LeafNode, RatchetTree}, @@ -494,3 +494,23 @@ pub struct StagedWelcome { // information. group: StagedCoreWelcome, } + +/// A parsed, but not fully processed `Welcome` message. +/// +/// This may be used in order to retrieve information from the `Welcome` about +/// the ratchet tree. +/// +/// Use `into_staged_welcome` to get the [`StagedWelcome`] on this. +pub struct ProcessedWelcome { + // The group configuration. See [`MlsGroupJoinConfig`] for more information. + mls_group_config: MlsGroupJoinConfig, + + // The following is the state after parsing the Welcome message, before actually + // building the group. + ciphersuite: Ciphersuite, + group_secrets: GroupSecrets, + key_schedule: crate::schedule::KeySchedule, + verifiable_group_info: crate::messages::group_info::VerifiableGroupInfo, + resumption_psk_store: crate::schedule::psk::store::ResumptionPskStore, + key_package_bundle: KeyPackageBundle, +} diff --git a/openmls/src/messages/group_info.rs b/openmls/src/messages/group_info.rs index 39871c9fdb..f5d9a7d750 100644 --- a/openmls/src/messages/group_info.rs +++ b/openmls/src/messages/group_info.rs @@ -103,7 +103,7 @@ impl VerifiableGroupInfo { /// Get (unverified) extensions of the verifiable group info. /// /// Note: This method should only be used when necessary to verify the group info signature. - pub(crate) fn extensions(&self) -> &Extensions { + pub fn extensions(&self) -> &Extensions { &self.payload.extensions } @@ -111,7 +111,7 @@ impl VerifiableGroupInfo { /// /// Note: This method should only be used when necessary to verify the group /// info signature. - pub(crate) fn group_id(&self) -> &GroupId { + pub fn group_id(&self) -> &GroupId { self.payload.group_context.group_id() } } diff --git a/openmls/src/messages/tests/test_welcome.rs b/openmls/src/messages/tests/test_welcome.rs index 76b73f9391..888f94fd89 100644 --- a/openmls/src/messages/tests/test_welcome.rs +++ b/openmls/src/messages/tests/test_welcome.rs @@ -9,7 +9,8 @@ use crate::{ }, extensions::Extensions, group::{ - errors::WelcomeError, GroupContext, GroupId, MlsGroup, MlsGroupCreateConfig, StagedWelcome, + errors::WelcomeError, GroupContext, GroupId, MlsGroup, MlsGroupCreateConfig, + ProcessedWelcome, StagedWelcome, }, messages::{ group_info::{GroupInfoTBS, VerifiableGroupInfo}, @@ -31,8 +32,6 @@ fn test_welcome_context_mismatch( ciphersuite: Ciphersuite, provider: &impl crate::storage::OpenMlsProvider, ) { - let _ = pretty_env_logger::try_init(); - // We need a ciphersuite that is different from the current one to create // the mismatch let mismatched_ciphersuite = match ciphersuite { @@ -297,6 +296,81 @@ fn test_welcome_message(ciphersuite: Ciphersuite, provider: &impl crate::storage ); } +/// Test the parsed welcome flow where the Welcome is first processed to give +/// the caller the GroupInfo. +/// This allows transporting information in the Welcome for retrieving the ratchet +/// tree. +#[openmls_test::openmls_test] +fn test_welcome_processing() { + let group_id = GroupId::random(provider.rand()); + let mls_group_create_config = MlsGroupCreateConfig::builder() + .ciphersuite(ciphersuite) + .build(); + + let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_signature_key) = + crate::group::test_core_group::setup_client("Alice", ciphersuite, provider); + let (_bob_credential, bob_kpb, _bob_signer, _bob_signature_key) = + crate::group::test_core_group::setup_client("Bob", ciphersuite, provider); + + let bob_kp = bob_kpb.key_package(); + + // === Alice creates a group and adds Bob === + let mut alice_group = MlsGroup::new_with_group_id( + provider, + &alice_signer, + &mls_group_create_config, + group_id, + alice_credential_with_key, + ) + .expect("An unexpected error occurred."); + + let (_queued_message, welcome, _group_info) = alice_group + .add_members(provider, &alice_signer, &[bob_kp.clone()]) + .expect("Could not add member to group."); + + alice_group + .merge_pending_commit(provider) + .expect("error merging pending commit"); + + let welcome = welcome.into_welcome().expect("Unexpected message type."); + + provider + .storage() + .write_key_package(&bob_kp.hash_ref(provider.crypto()).unwrap(), &bob_kpb) + .unwrap(); + + // Process the welcome + let processed_welcome = ProcessedWelcome::new_from_welcome( + provider, + mls_group_create_config.join_config(), + welcome, + ) + .unwrap(); + + // Check values in processed welcome + let unverified_group_info = processed_welcome.unverified_group_info(); + let group_id = unverified_group_info.group_id(); + assert_eq!(group_id, alice_group.group_id()); + let alice_group_info = alice_group + .export_group_info(provider, &alice_signer, false) + .unwrap() + .into_verifiable_group_info() + .unwrap(); + assert_eq!( + unverified_group_info.extensions(), + alice_group_info.extensions() + ); + // Use the group id or extensions to get the ratchet tree. + + // Stage the welcome + let staged_welcome = processed_welcome + .into_staged_welcome(provider, Some(alice_group.export_ratchet_tree().into())) + .unwrap(); + let _group = staged_welcome + .into_group(provider) + .expect("Error creating group from a valid staged join."); +} + #[test] fn invalid_welcomes() { // An almost good welcome message. From 96fef7af4882e15b35f8145664612719372f13d0 Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Tue, 4 Jun 2024 22:45:33 -0700 Subject: [PATCH 12/20] updated comments taking inspiration from add_members --- openmls/src/group/mls_group/proposal.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openmls/src/group/mls_group/proposal.rs b/openmls/src/group/mls_group/proposal.rs index bb204602fa..4a359e5606 100644 --- a/openmls/src/group/mls_group/proposal.rs +++ b/openmls/src/group/mls_group/proposal.rs @@ -388,11 +388,12 @@ impl MlsGroup { } /// Updates Group Context Extensions - /// + /// /// Commits to the Group Context Extension inline proposal using the [`Extensions`] /// /// Returns an error when the group does not support all the required capabilities - /// in the new `extensions`. + /// in the new `extensions` or if there is a pending commit. + /// // FIXME: #1217 #[allow(clippy::type_complexity)] pub fn update_group_context_extensions( &mut self, @@ -405,11 +406,12 @@ impl MlsGroup { > { self.is_operational()?; - // Create group context extension proposals + // Create inline group context extension proposals let inline_proposals = vec![Proposal::GroupContextExtensions( GroupContextExtensionProposal::new(extensions), )]; + // Create Commit over all proposals let params = CreateCommitParams::builder() .framing_parameters(self.framing_parameters()) .proposal_store(&self.proposal_store) @@ -418,6 +420,9 @@ impl MlsGroup { let create_commit_result = self.group.create_commit(params, provider, signer)?; let mls_messages = self.content_to_mls_message(create_commit_result.commit, provider)?; + + // Set the current group state to [`MlsGroupState::PendingCommit`], + // storing the current [`StagedCommit`] from the commit results self.group_state = MlsGroupState::PendingCommit(Box::new(PendingCommitState::Member( create_commit_result.staged_commit, ))); From fe15de6d1635577b867f7898ff4434a7ef8645f9 Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Tue, 4 Jun 2024 22:46:21 -0700 Subject: [PATCH 13/20] added test for update_group_context_extensions function --- openmls/src/group/mls_group/test_mls_group.rs | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/openmls/src/group/mls_group/test_mls_group.rs b/openmls/src/group/mls_group/test_mls_group.rs index 25fc1958f9..2c0edcac90 100644 --- a/openmls/src/group/mls_group/test_mls_group.rs +++ b/openmls/src/group/mls_group/test_mls_group.rs @@ -1553,6 +1553,137 @@ fn update_group_context_with_unknown_extension() { + let alice_provider = Provider::default(); + let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) = + setup_client("Alice", ciphersuite, &alice_provider); + + // === Define the unknown group context extension and initial data === + const UNKNOWN_EXTENSION_TYPE: u16 = 0xff11; + let unknown_extension_data = vec![1, 2]; + let unknown_gc_extension = Extension::Unknown( + UNKNOWN_EXTENSION_TYPE, + UnknownExtension(unknown_extension_data), + ); + let required_extension_types = &[ExtensionType::Unknown(UNKNOWN_EXTENSION_TYPE)]; + let required_capabilities = Extension::RequiredCapabilities( + RequiredCapabilitiesExtension::new(required_extension_types, &[], &[]), + ); + let capabilities = Capabilities::new(None, None, Some(required_extension_types), None, None); + let test_gc_extensions = Extensions::from_vec(vec![ + unknown_gc_extension.clone(), + required_capabilities.clone(), + ]) + .expect("error creating test group context extensions"); + let mls_group_create_config = MlsGroupCreateConfig::builder() + .with_group_context_extensions(test_gc_extensions.clone()) + .expect("error adding unknown extension to config") + .capabilities(capabilities.clone()) + .ciphersuite(ciphersuite) + .build(); + + // === Alice creates a group === + let mut alice_group = MlsGroup::new( + &alice_provider, + &alice_signer, + &mls_group_create_config, + alice_credential_with_key, + ) + .expect("error creating group"); + + // === Verify the initial group context extension data is correct === + let group_context_extensions = alice_group.group().context().extensions(); + let mut extracted_data = None; + for extension in group_context_extensions.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data = Some(data.clone()); + } + } + assert_eq!( + extracted_data.unwrap(), + vec![1, 2], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); + + // === Propose the new group context extension using update_group_context_extensions === + let updated_unknown_extension_data = vec![3, 4]; + let updated_unknown_gc_extension = Extension::Unknown( + UNKNOWN_EXTENSION_TYPE, + UnknownExtension(updated_unknown_extension_data.clone()), + ); + + let mut updated_extensions = test_gc_extensions.clone(); + updated_extensions.add_or_replace(updated_unknown_gc_extension); + + let update_result = alice_group.update_group_context_extensions( + &alice_provider, + updated_extensions, + &alice_signer, + ); + assert!( + update_result.is_ok(), + "Failed to update group context extensions: {:?}", + update_result.err() + ); + + // === Test clearing staged commit before merge, verify context shows expected data === + alice_group + .clear_pending_commit(provider.storage()) + .unwrap(); + let group_context_extensions = alice_group.group().context().extensions(); + let mut extracted_data = None; + for extension in group_context_extensions.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data = Some(data.clone()); + } + } + assert_eq!( + extracted_data.unwrap(), + vec![1, 2], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); + + // === Propose the new group context extension using update_group_context_extensions === + let updated_unknown_extension_data = vec![4, 5]; // Sample data for the extension + let updated_unknown_gc_extension = Extension::Unknown( + UNKNOWN_EXTENSION_TYPE, + UnknownExtension(updated_unknown_extension_data.clone()), + ); + + let mut updated_extensions = test_gc_extensions.clone(); + updated_extensions.add_or_replace(updated_unknown_gc_extension); + let update_result = alice_group.update_group_context_extensions( + &alice_provider, + updated_extensions, + &alice_signer, + ); + assert!( + update_result.is_ok(), + "Failed to update group context extensions: {:?}", + update_result.err() + ); + + // === Merge Pending Commit === + alice_group.merge_pending_commit(&alice_provider).unwrap(); + + // === Verify the group context extension was updated === + let group_context_extensions = alice_group.group().context().extensions(); + let mut extracted_data_updated = None; + for extension in group_context_extensions.iter() { + if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension { + extracted_data_updated = Some(data.clone()); + } + } + assert_eq!( + extracted_data_updated.unwrap(), + vec![4, 5], + "The data of Extension::Unknown(0xff11) does not match the expected data" + ); +} + // Test that unknown group context and leaf node extensions can be used in groups #[openmls_test] fn unknown_extensions() { From 0eae551105daf01e20ca998077c377faf414f02a Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Wed, 5 Jun 2024 10:43:04 +0200 Subject: [PATCH 14/20] silence clippy --- openmls/src/group/core_group/new_from_welcome.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/openmls/src/group/core_group/new_from_welcome.rs b/openmls/src/group/core_group/new_from_welcome.rs index 345faa00b7..eb8e46c669 100644 --- a/openmls/src/group/core_group/new_from_welcome.rs +++ b/openmls/src/group/core_group/new_from_welcome.rs @@ -91,6 +91,7 @@ impl StagedCoreWelcome { } } +#[allow(clippy::too_many_arguments)] pub(in crate::group) fn build_staged_welcome( verifiable_group_info: VerifiableGroupInfo, ratchet_tree: Option, From 5174c3118c9c43fd769fbc6ba469cc8e78b162af Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 5 Jun 2024 20:43:36 -0700 Subject: [PATCH 15/20] make the number of psks public --- openmls/src/group/mls_group/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmls/src/group/mls_group/config.rs b/openmls/src/group/mls_group/config.rs index faf4804adc..51115d4a80 100644 --- a/openmls/src/group/mls_group/config.rs +++ b/openmls/src/group/mls_group/config.rs @@ -49,7 +49,7 @@ pub struct MlsGroupJoinConfig { /// can be decrypted. The default is 0. pub(crate) max_past_epochs: usize, /// Number of resumption secrets to keep - pub(crate) number_of_resumption_psks: usize, + pub number_of_resumption_psks: usize, /// Flag to indicate the Ratchet Tree Extension should be used pub(crate) use_ratchet_tree_extension: bool, /// Sender ratchet configuration From edcc80b08db1214639f970f8fe3778f4a7439290 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Thu, 6 Jun 2024 13:19:33 +0200 Subject: [PATCH 16/20] Update openmls/src/group/mls_group/proposal.rs --- openmls/src/group/mls_group/proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmls/src/group/mls_group/proposal.rs b/openmls/src/group/mls_group/proposal.rs index 4a359e5606..849e2feb02 100644 --- a/openmls/src/group/mls_group/proposal.rs +++ b/openmls/src/group/mls_group/proposal.rs @@ -394,7 +394,7 @@ impl MlsGroup { /// Returns an error when the group does not support all the required capabilities /// in the new `extensions` or if there is a pending commit. /// // FIXME: #1217 - #[allow(clippy::type_complexity)] + //// FIXME: #1217 pub fn update_group_context_extensions( &mut self, provider: &Provider, From fac009495ce60800b79e870e8d4b66e6c1cb160b Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Thu, 6 Jun 2024 13:20:51 +0200 Subject: [PATCH 17/20] Update openmls/src/group/mls_group/proposal.rs --- openmls/src/group/mls_group/proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmls/src/group/mls_group/proposal.rs b/openmls/src/group/mls_group/proposal.rs index 849e2feb02..46cd8ee643 100644 --- a/openmls/src/group/mls_group/proposal.rs +++ b/openmls/src/group/mls_group/proposal.rs @@ -393,8 +393,8 @@ impl MlsGroup { /// /// Returns an error when the group does not support all the required capabilities /// in the new `extensions` or if there is a pending commit. - /// // FIXME: #1217 //// FIXME: #1217 + #[allow(clippy::type_complexity)] pub fn update_group_context_extensions( &mut self, provider: &Provider, From 703fb763a2eef40d401c9ecfcbc9bab188663b6a Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Thu, 6 Jun 2024 14:06:53 +0200 Subject: [PATCH 18/20] also expose PSKs from welcome --- openmls/src/group/mls_group/creation.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openmls/src/group/mls_group/creation.rs b/openmls/src/group/mls_group/creation.rs index d15caae04c..7aafcbc83b 100644 --- a/openmls/src/group/mls_group/creation.rs +++ b/openmls/src/group/mls_group/creation.rs @@ -11,7 +11,7 @@ use crate::{ group_info::{GroupInfo, VerifiableGroupInfo}, Welcome, }, - schedule::psk::store::ResumptionPskStore, + schedule::psk::{store::ResumptionPskStore, PreSharedKeyId}, storage::OpenMlsProvider, treesync::RatchetTreeIn, }; @@ -188,6 +188,13 @@ impl ProcessedWelcome { &self.verifiable_group_info } + /// Get a reference to the PSKs in this Welcome message. + /// + /// **NOTE:** The group info contains **unverified** values. Use with caution. + pub fn psks(&self) -> &[PreSharedKeyId] { + &self.group_secrets.psks + } + /// Consume the `ProcessedWelcome` and combine it witht he ratchet tree into /// a `StagedWelcome`. pub fn into_staged_welcome( From 2fa5d2d10b263db13c73d243962cdcc729d0a3ff Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 6 Jun 2024 21:39:40 -0700 Subject: [PATCH 19/20] dont make the crate public --- openmls/src/group/mls_group/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmls/src/group/mls_group/config.rs b/openmls/src/group/mls_group/config.rs index 51115d4a80..faf4804adc 100644 --- a/openmls/src/group/mls_group/config.rs +++ b/openmls/src/group/mls_group/config.rs @@ -49,7 +49,7 @@ pub struct MlsGroupJoinConfig { /// can be decrypted. The default is 0. pub(crate) max_past_epochs: usize, /// Number of resumption secrets to keep - pub number_of_resumption_psks: usize, + pub(crate) number_of_resumption_psks: usize, /// Flag to indicate the Ratchet Tree Extension should be used pub(crate) use_ratchet_tree_extension: bool, /// Sender ratchet configuration From 718959cee905a1c78de00f72dbce9f123f332d88 Mon Sep 17 00:00:00 2001 From: raphaelrobert Date: Fri, 7 Jun 2024 15:41:18 +0200 Subject: [PATCH 20/20] Attempted fix for test runner (#1587) --- .github/workflows/interop.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/interop.yml b/.github/workflows/interop.yml index 161176372d..5dd45ec562 100644 --- a/.github/workflows/interop.yml +++ b/.github/workflows/interop.yml @@ -84,6 +84,8 @@ jobs: make run-go || echo "Build despite errors." cd test-runner # TODO(#1366) + go get -u google.golang.org/grpc + go mod tidy -e patch main.go main.go.patch go build