Skip to content

Commit

Permalink
Enable the use of unknown/opaque extensions (openmls#1479)
Browse files Browse the repository at this point in the history
  • Loading branch information
kkohbrok authored Jan 19, 2024
1 parent 0d67da8 commit 303967c
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1475](https://github.com/openmls/openmls/pull/1475): Fully process GroupContextExtension proposals
- [#1477](https://github.com/openmls/openmls/pull/1477): Allow setting leaf node extensions and capabilities of the group creator when creating an MlsGroup(Config)
- [#1478](https://github.com/openmls/openmls/pull/1478): Remove explicit functions to set `RequiredCapabilitiesExtension` and `ExternalSendersExtension` when building an MlsGroup(Config) in favor of the more general function to set group context extensions
- [#1479](https://github.com/openmls/openmls/pull/1479): Allow the use of extensions with `ExtensionType::Unknown` in group context, key packages and leaf nodes

## 0.5.0 (XXXX-XX-XX)

Expand Down
4 changes: 4 additions & 0 deletions book/src/user_manual/group_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ Example create configuration:
```rust,no_run,noplayground
{{#include ../../../openmls/tests/book_code.rs:mls_group_create_config_example}}
```

## Unknown extensions

Some extensions carry data, but don't alter the behaviour of the protocol (e.g. the application_id extension). OpenMLS allows the use of arbitrary such extensions in the group context, key packages and leaf nodes. Such extensions can be instantiated and retrieved through the use of the `UnknownExtension` struct and the `ExtensionType::Unknown` extension type. Such "unknown" extensions are handled transparently by OpenMLS, but can be used by the application, e.g. to have a group agree on pieces of data.
15 changes: 0 additions & 15 deletions openmls/src/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,21 +167,6 @@ impl From<ExtensionType> for u16 {
}
}

impl ExtensionType {
/// Check whether an [`ExtensionType`] is supported or not.
pub fn is_supported(&self) -> bool {
matches!(
self,
ExtensionType::ApplicationId
| ExtensionType::RatchetTree
| ExtensionType::RequiredCapabilities
| ExtensionType::ExternalPub
| ExtensionType::ExternalSenders
| ExtensionType::LastResort
)
}
}

/// # Extension
///
/// An extension is one of the [`Extension`] enum values.
Expand Down
5 changes: 0 additions & 5 deletions openmls/src/extensions/required_capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@ impl RequiredCapabilitiesExtension {

/// Check if all extension and proposal types are supported.
pub(crate) fn check_support(&self) -> Result<(), ExtensionError> {
for extension in self.extension_types() {
if !extension.is_supported() {
return Err(ExtensionError::UnsupportedExtensionType);
}
}
for proposal in self.proposal_types() {
if !proposal.is_supported() {
return Err(ExtensionError::UnsupportedProposalType);
Expand Down
72 changes: 50 additions & 22 deletions openmls/src/group/core_group/test_proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,56 @@ fn test_required_unsupported_proposals(ciphersuite: Ciphersuite, provider: &impl
)
}

#[apply(ciphersuites_and_providers)]
fn test_required_extension_key_package_mismatch(
ciphersuite: Ciphersuite,
provider: &impl OpenMlsProvider,
) {
// Basic group setup.
let group_aad = b"Alice's test group";
let framing_parameters = FramingParameters::new(group_aad, WireFormat::PublicMessage);

let (alice_credential, _, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, provider);
let (_bob_credential_with_key, bob_key_package_bundle, _, _) =
setup_client("Bob", ciphersuite, provider);
let bob_key_package = bob_key_package_bundle.key_package();

// Set required capabilities
let extensions = &[ExtensionType::Unknown(0xff00)];
// We don't support unknown proposals (yet)
let proposals = &[];
let credentials = &[CredentialType::Basic];
let required_capabilities =
RequiredCapabilitiesExtension::new(extensions, proposals, credentials);

let alice_group = CoreGroup::builder(
GroupId::random(provider.rand()),
CryptoConfig::with_default_version(ciphersuite),
alice_credential,
)
.with_group_context_extensions(Extensions::single(Extension::RequiredCapabilities(
required_capabilities,
)))
.expect("error adding group context extensions")
.build(provider, &alice_signer)
.expect("Error creating CoreGroup.");

let e = alice_group
.create_add_proposal(
framing_parameters,
bob_key_package.clone(),
&alice_signer,
)
.expect_err("Proposal was created even though the key package didn't support the required extensions.");
assert_eq!(
e,
CreateAddProposalError::LeafNodeValidation(
crate::treesync::errors::LeafNodeValidationError::UnsupportedExtensions
)
);
}

#[apply(ciphersuites_and_providers)]
fn test_group_context_extensions(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
// Basic group setup.
Expand Down Expand Up @@ -430,28 +480,6 @@ fn test_group_context_extension_proposal_fails(
.build(provider, &alice_signer)
.expect("Error creating CoreGroup.");

// TODO: openmls/openmls#1130 add a test for unsupported required capabilities.
// We can't test this right now because we don't have a capability
// that is not a "default" proposal or extension.
// // Alice tries to add a required capability she doesn't support herself.
// let required_application_id = Extension::RequiredCapabilities(
// RequiredCapabilitiesExtension::new(&[ExtensionType::ApplicationId], &[]),
// );
// let e = alice_group.create_group_context_ext_proposal(
// framing_parameters,
// &alice_credential_bundle,
// &[required_application_id.clone()],
// provider,
// ).expect_err("Alice was able to create a gce proposal with a required extensions she doesn't support.");
// assert_eq!(
// e,
// CreateGroupContextExtProposalError::TreeSyncError(
// crate::treesync::errors::TreeSyncError::UnsupportedExtension
// )
// );
//
// // Well, this failed luckily.

// Adding Bob
let bob_add_proposal = alice_group
.create_add_proposal(framing_parameters, bob_key_package.clone(), &alice_signer)
Expand Down
111 changes: 102 additions & 9 deletions openmls/src/group/mls_group/test_mls_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,10 +1101,19 @@ fn builder_pattern(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
let test_lifetime = Lifetime::new(3600);
let test_wire_format_policy = PURE_CIPHERTEXT_WIRE_FORMAT_POLICY;
let test_padding_size = 100;
let test_external_senders = vec![ExternalSender::new(
let test_external_senders = Extension::ExternalSenders(vec![ExternalSender::new(
alice_credential_with_key.signature_key.clone(),
alice_credential_with_key.credential.clone(),
)];
)]);
let test_required_capabilities = Extension::RequiredCapabilities(
RequiredCapabilitiesExtension::new(&[ExtensionType::Unknown(0xff00)], &[], &[]),
);
let test_gc_extensions = Extensions::from_vec(vec![
test_external_senders.clone(),
test_required_capabilities.clone(),
])
.expect("error creating group context extensions");

let test_crypto_config = CryptoConfig::with_default_version(ciphersuite);
let test_sender_ratchet_config = SenderRatchetConfiguration::new(10, 2000);
let test_max_past_epochs = 10;
Expand All @@ -1126,18 +1135,16 @@ fn builder_pattern(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
.with_group_id(test_group_id.clone())
.padding_size(test_padding_size)
.sender_ratchet_configuration(test_sender_ratchet_config.clone())
.with_group_context_extensions(Extensions::single(Extension::ExternalSenders(
test_external_senders.clone(),
)))
.unwrap()
.with_group_context_extensions(test_gc_extensions.clone())
.expect("error adding group context extension to builder")
.crypto_config(test_crypto_config)
.with_wire_format_policy(test_wire_format_policy)
.lifetime(test_lifetime)
.use_ratchet_tree_extension(true)
.max_past_epochs(test_max_past_epochs)
.number_of_resumption_psks(test_number_of_resumption_psks)
.with_leaf_node_extensions(test_leaf_extensions.clone())
.unwrap()
.expect("error adding leaf node extension to builder")
.with_capabilities(test_capabilities.clone())
.build(provider, &alice_signer, alice_credential_with_key)
.expect("error creating group using builder");
Expand Down Expand Up @@ -1165,13 +1172,19 @@ fn builder_pattern(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
let external_senders = group_context
.extensions()
.external_senders()
.expect("error getting external senders");
assert_eq!(external_senders, &test_external_senders);
.expect("error getting external senders")
.to_vec();
assert_eq!(
Extension::ExternalSenders(external_senders),
test_external_senders
);
let crypto_config = CryptoConfig {
ciphersuite,
version: group_context.protocol_version(),
};
assert_eq!(crypto_config, test_crypto_config);
let extensions = group_context.extensions();
assert_eq!(extensions, &test_gc_extensions);
let lifetime = alice_group
.own_leaf()
.expect("error getting own leaf")
Expand All @@ -1195,3 +1208,83 @@ fn builder_pattern(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
.expect_err("successfully built group with invalid leaf extensions");
assert_eq!(builder_err, InvalidExtensionError::IllegalInLeafNodes);
}

// Test that unknown group context and leaf node extensions can be used in groups
#[apply(ciphersuites_and_providers)]
fn unknown_extensions(ciphersuite: Ciphersuite, provider: &impl OpenMlsProvider) {
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, provider);

let unknown_gc_extension = Extension::Unknown(0xff00, UnknownExtension(vec![0, 1, 2, 3]));
let unknown_leaf_extension = Extension::Unknown(0xff01, UnknownExtension(vec![4, 5, 6, 7]));
let unknown_kp_extension = Extension::Unknown(0xff02, UnknownExtension(vec![8, 9, 10, 11]));
let required_extensions = &[
ExtensionType::Unknown(0xff00),
ExtensionType::Unknown(0xff01),
];
let required_capabilities =
Extension::RequiredCapabilities(RequiredCapabilitiesExtension::new(&[], &[], &[]));
let capabilities = Capabilities::new(None, None, Some(required_extensions), None, None);
let test_gc_extensions = Extensions::from_vec(vec![
unknown_gc_extension.clone(),
required_capabilities.clone(),
])
.expect("error creating group context extensions");
let test_kp_extensions = Extensions::single(unknown_kp_extension.clone());

// === Alice creates a group ===
let config = CryptoConfig {
ciphersuite,
version: crate::versions::ProtocolVersion::default(),
};
let mut alice_group = MlsGroup::builder()
.crypto_config(config)
.with_capabilities(capabilities.clone())
.with_leaf_node_extensions(Extensions::single(unknown_leaf_extension.clone()))
.expect("error adding unknown leaf extension to builder")
.with_group_context_extensions(test_gc_extensions.clone())
.expect("error adding unknown extension to builder")
.build(provider, &alice_signer, alice_credential_with_key)
.expect("error creating group using builder");

// Check that everything was added successfully
let group_context = alice_group.export_group_context();
assert_eq!(group_context.extensions(), &test_gc_extensions);
let leaf_node = alice_group.own_leaf().expect("error getting own leaf");
assert_eq!(
leaf_node.extensions(),
&Extensions::single(unknown_leaf_extension)
);

// Now let's add Bob to the group and make sure that he joins the group successfully

// === Alice adds Bob ===
let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, provider);

// Generate a KP that supports the unknown extensions
let bob_key_package = KeyPackage::builder()
.leaf_node_capabilities(capabilities)
.key_package_extensions(test_kp_extensions.clone())
.build(config, provider, &bob_signer, bob_credential_with_key)
.expect("error building key package");

assert_eq!(
bob_key_package.extensions(),
&Extensions::single(unknown_kp_extension)
);

// alice adds bob and bob processes the welcome to ensure that the unknown
// extensions are processed correctly
let (_, welcome, _) = alice_group
.add_members(provider, &alice_signer, &[bob_key_package.clone()])
.unwrap();
alice_group.merge_pending_commit(provider).unwrap();
let _bob_group = MlsGroup::new_from_welcome(
provider,
&MlsGroupJoinConfig::default(),
welcome.into_welcome().unwrap(),
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating group from Welcome");
}

0 comments on commit 303967c

Please sign in to comment.