Skip to content

Commit

Permalink
Add with_group_context_extensions method to group builders (openmls…
Browse files Browse the repository at this point in the history
…#1473)

Add a method with_group_context_extensions to the group builders, that sets an arbitrary set of extensions. The set is of type Extensions, so we know that each extension is only set once.

Any RequiredCapability extension mentioned in that set will be overwritten, and if ExternalSenders is specified using the dedicated method, that one is preferred and the one provided using with_group_context_extensions is dropped.
  • Loading branch information
keks authored Jan 11, 2024
1 parent 741316d commit 596adef
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 15 deletions.
8 changes: 8 additions & 0 deletions book/src/user_manual/create_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ If someone else already gave you a group ID, e.g., a provider server, you can al
```rust,no_run,noplayground
{{#include ../../../openmls/tests/book_code.rs:alice_create_group_with_group_id}}
```

The Builder provides methods for setting required capabilities and external senders.
The information passed into these lands in the group context, in the form of extensions.
Should the user want to add further extensions, they can use the `with_group_context_extensions` method:

```rust,no_run,noplayground
{{#include ../../../openmls/tests/book_code.rs:alice_create_group_with_builder_with_extensions}}
```
5 changes: 5 additions & 0 deletions openmls/src/extensions/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,9 @@ pub enum InvalidExtensionError {
/// The specified extension could not be found.
#[error("The specified extension could not be found.")]
NotFound,
/// The provided extension list contains an extension that is not allowed in group contexts
#[error(
"The provided extension list contains an extension that is not allowed in group contexts."
)]
IllegalInGroupContext,
}
121 changes: 120 additions & 1 deletion openmls/src/extensions/test_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::{
group::{config::CryptoConfig, errors::*, *},
key_packages::*,
messages::proposals::ProposalType,
prelude::Capabilities,
prelude::{Capabilities, RatchetTreeIn},
prelude_test::HpkePublicKey,
schedule::psk::store::ResumptionPskStore,
test_utils::*,
versions::ProtocolVersion,
Expand Down Expand Up @@ -248,6 +249,124 @@ fn required_capabilities() {
assert_eq!(extension_bytes, encoded);
}

#[apply(ciphersuites_and_providers)]
fn with_group_context_extensions(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
// create an extension that we can check for later
let test_extension = Extension::Unknown(0xf023, UnknownExtension(vec![0xca, 0xfe]));
let extensions = Extensions::single(test_extension.clone());

let alice_credential_with_key_and_signer = tests::utils::generate_credential_with_key(
"Alice".into(),
ciphersuite.signature_algorithm(),
provider,
);

let mls_group_create_config = MlsGroupCreateConfig::builder()
.with_group_context_extensions(extensions)
.expect("failed to apply extensions at group config builder")
.crypto_config(CryptoConfig::with_default_version(ciphersuite))
.build();

// === Alice creates a group ===
let alice_group = MlsGroup::new(
provider,
&alice_credential_with_key_and_signer.signer,
&mls_group_create_config,
alice_credential_with_key_and_signer.credential_with_key,
)
.expect("An unexpected error occurred.");

// === Group contains extension ===
let found_test_extension = alice_group
.export_group_context()
.extensions()
.find_by_type(ExtensionType::Unknown(0xf023))
.expect("failed to get test extensions from group context");
assert_eq!(found_test_extension, &test_extension);
}

#[apply(ciphersuites_and_providers)]
fn wrong_extension_with_group_context_extensions(
ciphersuite: Ciphersuite,
provider: &impl OpenMlsProvider,
) {
// Extension types that are known to not be allowed here:
// - application id
// - external pub
// - ratchet tree

let alice_credential_with_key_and_signer = tests::utils::generate_credential_with_key(
"Alice".into(),
ciphersuite.signature_algorithm(),
provider,
);

let crypto_config = CryptoConfig::with_default_version(ciphersuite);

// create an extension that we can check for later
let test_extension = Extension::ApplicationId(ApplicationIdExtension::new(&[0xca, 0xfe]));
let extensions = Extensions::single(test_extension.clone());

let err = MlsGroup::builder()
.with_group_context_extensions(extensions.clone())
.expect_err("builder accepted non-group-context extension");

assert_eq!(err, InvalidExtensionError::IllegalInGroupContext);
let err = PublicGroup::builder(
GroupId::from_slice(&[0xbe, 0xef]),
crypto_config,
alice_credential_with_key_and_signer
.credential_with_key
.clone(),
)
.with_group_context_extensions(extensions)
.expect_err("builder accepted non-group-context extension");

assert_eq!(err, InvalidExtensionError::IllegalInGroupContext);
// create an extension that we can check for later
let test_extension =
Extension::ExternalPub(ExternalPubExtension::new(HpkePublicKey::new(vec![])));
let extensions = Extensions::single(test_extension.clone());

let err = MlsGroup::builder()
.with_group_context_extensions(extensions.clone())
.expect_err("builder accepted non-group-context extension");
assert_eq!(err, InvalidExtensionError::IllegalInGroupContext);

let err = PublicGroup::builder(
GroupId::from_slice(&[0xbe, 0xef]),
crypto_config,
alice_credential_with_key_and_signer
.credential_with_key
.clone(),
)
.with_group_context_extensions(extensions)
.expect_err("builder accepted non-group-context extension");
assert_eq!(err, InvalidExtensionError::IllegalInGroupContext);

// create an extension that we can check for later
let test_extension = Extension::RatchetTree(RatchetTreeExtension::new(
RatchetTreeIn::from_nodes(vec![]).into(),
));
let extensions = Extensions::single(test_extension.clone());

let err = MlsGroup::builder()
.with_group_context_extensions(extensions.clone())
.expect_err("builder accepted non-group-context extension");
assert_eq!(err, InvalidExtensionError::IllegalInGroupContext);

let err = PublicGroup::builder(
GroupId::from_slice(&[0xbe, 0xef]),
crypto_config,
alice_credential_with_key_and_signer
.credential_with_key
.clone(),
)
.with_group_context_extensions(extensions)
.expect_err("builder accepted non-group-context extension");
assert_eq!(err, InvalidExtensionError::IllegalInGroupContext);
}

#[apply(ciphersuites_and_providers)]
fn last_resort_extension(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
let last_resort = Extension::LastResort(LastResortExtension::default());
Expand Down
17 changes: 17 additions & 0 deletions openmls/src/group/core_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ use crate::{
ciphersuite::{signable::Signable, HpkePublicKey},
credentials::*,
error::LibraryError,
extensions::errors::InvalidExtensionError,
framing::{mls_auth_content::AuthenticatedContent, *},
group::{config::CryptoConfig, *},
key_packages::*,
Expand Down Expand Up @@ -200,6 +201,22 @@ impl CoreGroupBuilder {
}
self
}

/// Sets initial group context extensions. Note that RequiredCapabilities
/// extensions will be overwritten, and should be set using a call to
/// `required_capabilities`. If `ExternalSenders` extensions are provided
/// both in this call, and a call to `external_senders`, only the one from
/// the call to `external_senders` will be included.
pub(crate) fn with_group_context_extensions(
mut self,
extensions: Extensions,
) -> Result<Self, InvalidExtensionError> {
self.public_group_builder = self
.public_group_builder
.with_group_context_extensions(extensions)?;
Ok(self)
}

/// Set the number of past epochs the group should keep secrets.
pub fn with_max_past_epoch_secrets(mut self, max_past_epochs: usize) -> Self {
self.max_past_epochs = max_past_epochs;
Expand Down
16 changes: 14 additions & 2 deletions openmls/src/group/mls_group/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use openmls_traits::{key_store::OpenMlsKeyStore, signatures::Signer, OpenMlsProv

use crate::{
credentials::CredentialWithKey,
extensions::ExternalSendersExtension,
extensions::{errors::InvalidExtensionError, Extensions, ExternalSendersExtension},
group::{
config::CryptoConfig, public_group::errors::PublicGroupBuildError, CoreGroup,
CoreGroupBuildError, CoreGroupConfig, GroupId, MlsGroupCreateConfig,
Expand All @@ -13,7 +13,7 @@ use crate::{

use super::{InnerState, MlsGroup, MlsGroupState};

#[derive(Default)]
#[derive(Default, Debug)]
pub struct MlsGroupBuilder {
group_id: Option<GroupId>,
mls_group_create_config_builder: MlsGroupCreateConfigBuilder,
Expand Down Expand Up @@ -72,6 +72,7 @@ impl MlsGroupBuilder {
.with_config(group_config)
.with_required_capabilities(mls_group_create_config.required_capabilities.clone())
.with_external_senders(mls_group_create_config.external_senders.clone())
.with_group_context_extensions(mls_group_create_config.group_context_extensions.clone())?
.with_max_past_epoch_secrets(mls_group_create_config.join_config.max_past_epochs)
.with_lifetime(*mls_group_create_config.lifetime())
.build(provider, signer)
Expand Down Expand Up @@ -198,4 +199,15 @@ impl MlsGroupBuilder {
.external_senders(external_senders);
self
}

/// Sets the initial group context extensions
pub fn with_group_context_extensions(
mut self,
extensions: Extensions,
) -> Result<Self, InvalidExtensionError> {
self.mls_group_create_config_builder = self
.mls_group_create_config_builder
.with_group_context_extensions(extensions)?;
Ok(self)
}
}
32 changes: 30 additions & 2 deletions openmls/src/group/mls_group/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
use super::*;
use crate::{
group::config::CryptoConfig, key_packages::Lifetime,
extensions::errors::InvalidExtensionError, group::config::CryptoConfig, key_packages::Lifetime,
tree::sender_ratchet::SenderRatchetConfiguration,
};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -93,6 +93,8 @@ pub struct MlsGroupCreateConfig {
pub(crate) crypto_config: CryptoConfig,
/// Configuration parameters relevant to group operation at runtime
pub(crate) join_config: MlsGroupJoinConfig,
/// List of initial group context extensions
pub(crate) group_context_extensions: Extensions,
}

/// Builder struct for an [`MlsGroupJoinConfig`].
Expand Down Expand Up @@ -200,6 +202,13 @@ impl MlsGroupCreateConfig {
&self.external_senders
}

/// Returns the [`Extensions`] set as the initial group context.
/// This does not contain the initial group context extensions
/// added from builder calls to `external_senders` or `required_capabilities`.
pub fn group_context_extensions(&self) -> &Extensions {
&self.group_context_extensions
}

/// Returns the [`MlsGroupCreateConfig`] lifetime configuration.
pub fn lifetime(&self) -> &Lifetime {
&self.lifetime
Expand Down Expand Up @@ -228,7 +237,7 @@ impl MlsGroupCreateConfig {
}

/// Builder for an [`MlsGroupCreateConfig`].
#[derive(Default)]
#[derive(Default, Debug)]
pub struct MlsGroupCreateConfigBuilder {
config: MlsGroupCreateConfig,
}
Expand Down Expand Up @@ -317,6 +326,25 @@ impl MlsGroupCreateConfigBuilder {
self
}

/// Sets initial group context extensions. Note that RequiredCapabilities
/// extensions will be overwritten, and should be set using a call to
/// `required_capabilities`. If `ExternalSenders` extensions are provided
/// both in this call, and a call to `external_senders`, only the one from
/// the call to `external_senders` will be included.
pub fn with_group_context_extensions(
mut self,
extensions: Extensions,
) -> Result<Self, InvalidExtensionError> {
let is_valid_in_group_context = extensions.application_id().is_none()
&& extensions.ratchet_tree().is_none()
&& extensions.external_pub().is_none();
if !is_valid_in_group_context {
return Err(InvalidExtensionError::IllegalInGroupContext);
}
self.config.group_context_extensions = extensions;
Ok(self)
}

/// Finalizes the builder and retursn an `[MlsGroupCreateConfig`].
pub fn build(self) -> MlsGroupCreateConfig {
self.config
Expand Down
2 changes: 1 addition & 1 deletion openmls/src/group/mls_group/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub enum NewGroupError<KeyStoreError> {
UnsupportedExtensionType,
/// Invalid extensions set in configuration
#[error("Invalid extensions set in configuration")]
InvalidExtensions(InvalidExtensionError),
InvalidExtensions(#[from] InvalidExtensionError),
}

/// EmptyInput error
Expand Down
40 changes: 31 additions & 9 deletions openmls/src/group/public_group/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{
credentials::CredentialWithKey,
error::LibraryError,
extensions::{
errors::ExtensionError, Extension, Extensions, ExternalSendersExtension,
RequiredCapabilitiesExtension,
errors::{ExtensionError, InvalidExtensionError},
Extension, Extensions, ExternalSendersExtension, RequiredCapabilitiesExtension,
},
group::{config::CryptoConfig, GroupContext, GroupId},
key_packages::Lifetime,
Expand All @@ -18,6 +18,7 @@ use crate::{
},
};

#[derive(Debug)]
pub(crate) struct TempBuilderPG1 {
group_id: GroupId,
crypto_config: CryptoConfig,
Expand All @@ -26,6 +27,7 @@ pub(crate) struct TempBuilderPG1 {
required_capabilities: Option<RequiredCapabilitiesExtension>,
external_senders: Option<ExternalSendersExtension>,
leaf_extensions: Option<Extensions>,
group_context_extensions: Option<Extensions>,
}

impl TempBuilderPG1 {
Expand All @@ -52,6 +54,20 @@ impl TempBuilderPG1 {
self
}

pub(crate) fn with_group_context_extensions(
mut self,
extensions: Extensions,
) -> Result<Self, InvalidExtensionError> {
let is_valid_in_group_context = extensions.application_id().is_none()
&& extensions.ratchet_tree().is_none()
&& extensions.external_pub().is_none();
if !is_valid_in_group_context {
return Err(InvalidExtensionError::IllegalInGroupContext);
}
self.group_context_extensions = Some(extensions);
Ok(self)
}

pub(crate) fn get_secrets(
self,
provider: &impl OpenMlsProvider,
Expand Down Expand Up @@ -87,17 +103,22 @@ impl TempBuilderPG1 {
_ => LibraryError::custom("Unexpected ExtensionError").into(),
})?;
let required_capabilities = Extension::RequiredCapabilities(required_capabilities);
let extensions =
if let Some(ext_senders) = self.external_senders.map(Extension::ExternalSenders) {
vec![required_capabilities, ext_senders]
} else {
vec![required_capabilities]
};

let mut group_context_extensions = if let Some(exts) = self.group_context_extensions {
exts
} else {
Extensions::empty()
};
group_context_extensions.add_or_replace(required_capabilities);
if let Some(ext_senders) = self.external_senders {
group_context_extensions.add_or_replace(Extension::ExternalSenders(ext_senders));
}

let group_context = GroupContext::create_initial_group_context(
self.crypto_config.ciphersuite,
self.group_id,
treesync.tree_hash().to_vec(),
Extensions::from_vec(extensions)?,
group_context_extensions,
);
let next_builder = TempBuilderPG2 {
treesync,
Expand Down Expand Up @@ -172,6 +193,7 @@ impl PublicGroup {
required_capabilities: None,
external_senders: None,
leaf_extensions: None,
group_context_extensions: None,
}
}
}
Loading

0 comments on commit 596adef

Please sign in to comment.