Skip to content

Commit

Permalink
add checks
Browse files Browse the repository at this point in the history
  • Loading branch information
keks committed Sep 18, 2024
1 parent 25317f1 commit a53a332
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 80 deletions.
28 changes: 28 additions & 0 deletions openmls/src/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,34 @@ pub enum ExtensionType {
Unknown(u16),
}

impl ExtensionType {
/// Returns true for all extension types that are considered "default" by the spec.
pub(crate) fn is_default(self) -> bool {
match self {
ExtensionType::ApplicationId
| ExtensionType::RatchetTree
| ExtensionType::RequiredCapabilities
| ExtensionType::ExternalPub
| ExtensionType::ExternalSenders => true,
ExtensionType::LastResort | ExtensionType::Unknown(_) => false,
}
}

/// Returns whether an extension type is valid when used in leaf nodes.
/// Returns None if validity can not be determined.
pub(crate) fn is_valid_in_leaf_node(self) -> Option<bool> {
match self {
ExtensionType::ApplicationId
| ExtensionType::RatchetTree
| ExtensionType::RequiredCapabilities
| ExtensionType::ExternalPub
| ExtensionType::ExternalSenders => Some(false),
ExtensionType::LastResort => Some(true),
ExtensionType::Unknown(_) => None,
}
}
}

impl Size for ExtensionType {
fn tls_serialized_len(&self) -> usize {
2
Expand Down
7 changes: 2 additions & 5 deletions openmls/src/extensions/required_capabilities.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};

use crate::{
credentials::CredentialType, messages::proposals::ProposalType,
treesync::node::leaf_node::default_extensions,
};
use crate::{credentials::CredentialType, messages::proposals::ProposalType};

use super::{Deserialize, ExtensionType, Serialize};

Expand Down Expand Up @@ -80,6 +77,6 @@ impl RequiredCapabilitiesExtension {

/// Checks whether support for the provided extension type is required.
pub(crate) fn requires_extension_type_support(&self, ext_type: ExtensionType) -> bool {
self.extension_types.contains(&ext_type) || default_extensions().contains(&ext_type)
self.extension_types.contains(&ext_type)
}
}
4 changes: 2 additions & 2 deletions openmls/src/group/mls_group/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
},
storage::OpenMlsProvider,
tree::sender_ratchet::SenderRatchetConfiguration,
treesync::node::leaf_node::Capabilities,
treesync::{errors::LeafNodeValidationError, node::leaf_node::Capabilities},
};

use super::{past_secrets::MessageSecretsStore, MlsGroup, MlsGroupState};
Expand Down Expand Up @@ -263,7 +263,7 @@ impl MlsGroupBuilder {
pub fn with_leaf_node_extensions(
mut self,
extensions: Extensions,
) -> Result<Self, InvalidExtensionError> {
) -> Result<Self, LeafNodeValidationError> {
self.mls_group_create_config_builder = self
.mls_group_create_config_builder
.with_leaf_node_extensions(extensions)?;
Expand Down
18 changes: 14 additions & 4 deletions openmls/src/group/mls_group/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
use super::*;
use crate::{
extensions::errors::InvalidExtensionError, key_packages::Lifetime,
tree::sender_ratchet::SenderRatchetConfiguration, treesync::node::leaf_node::Capabilities,
extensions::errors::InvalidExtensionError,
key_packages::Lifetime,
tree::sender_ratchet::SenderRatchetConfiguration,
treesync::{errors::LeafNodeValidationError, node::leaf_node::Capabilities},
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -339,15 +341,23 @@ impl MlsGroupCreateConfigBuilder {
pub fn with_leaf_node_extensions(
mut self,
extensions: Extensions,
) -> Result<Self, InvalidExtensionError> {
) -> Result<Self, LeafNodeValidationError> {
// None of the default extensions are leaf node extensions, so only
// unknown extensions can be leaf node extensions.
let is_valid_in_leaf_node = extensions
.iter()
.all(|e| matches!(e.extension_type(), ExtensionType::Unknown(_)));
if !is_valid_in_leaf_node {
return Err(InvalidExtensionError::IllegalInLeafNodes);
return Err(LeafNodeValidationError::UnsupportedExtensions);
}

// Make sure that the extension type is supported in this context.
// This means that the leaf node needs to have support listed in the
// the capabilities.
if !self.config.capabilities.contains_extensions(&extensions) {
return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
}

self.config.leaf_node_extensions = extensions;
Ok(self)
}
Expand Down
11 changes: 7 additions & 4 deletions openmls/src/group/mls_group/tests_and_kats/tests/mls_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use tls_codec::{Deserialize, Serialize};
use crate::{
binary_tree::LeafNodeIndex,
credentials::test_utils::new_credential,
extensions::errors::InvalidExtensionError,
framing::*,
group::{errors::*, *},
key_packages::*,
Expand All @@ -28,7 +27,11 @@ use crate::{
},
},
tree::sender_ratchet::SenderRatchetConfiguration,
treesync::{errors::ApplyUpdatePathError, node::leaf_node::Capabilities, LeafNodeParameters},
treesync::{
errors::{ApplyUpdatePathError, LeafNodeValidationError},
node::leaf_node::Capabilities,
LeafNodeParameters,
},
};

#[openmls_test]
Expand Down Expand Up @@ -1206,9 +1209,9 @@ fn builder_pattern() {
.use_ratchet_tree_extension(true)
.max_past_epochs(test_max_past_epochs)
.number_of_resumption_psks(test_number_of_resumption_psks)
.with_capabilities(test_capabilities.clone())
.with_leaf_node_extensions(test_leaf_extensions.clone())
.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 @@ -1265,7 +1268,7 @@ fn builder_pattern() {
let builder_err = MlsGroup::builder()
.with_leaf_node_extensions(invalid_leaf_extensions)
.expect_err("successfully built group with invalid leaf extensions");
assert_eq!(builder_err, InvalidExtensionError::IllegalInLeafNodes);
assert_eq!(builder_err, LeafNodeValidationError::UnsupportedExtensions);
}

// Test the successful update of Group Context Extension with type Extension::Unknown(0xff11)
Expand Down
8 changes: 6 additions & 2 deletions openmls/src/group/public_group/errors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use thiserror::Error;

use crate::{
error::LibraryError, extensions::errors::InvalidExtensionError,
treesync::errors::TreeSyncFromNodesError,
error::LibraryError,
extensions::errors::InvalidExtensionError,
treesync::errors::{LeafNodeValidationError, TreeSyncFromNodesError},
};

/// Public group creation from external error.
Expand All @@ -26,6 +27,9 @@ pub enum CreationFromExternalError<StorageError> {
/// We don't support the version of the group we are trying to join.
#[error("We don't support the version of the group we are trying to join.")]
UnsupportedMlsVersion,
/// See [`LeafNodeValidationError`]
#[error(transparent)]
LeafNodeValidation(#[from] LeafNodeValidationError),
/// Error writing to storage
#[error("Error writing to storage: {0}")]
WriteToStorageError(StorageError),
Expand Down
13 changes: 13 additions & 0 deletions openmls/src/group/public_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ impl PublicGroup {
// signature against.
let treesync = TreeSync::from_ratchet_tree(crypto, ciphersuite, ratchet_tree)?;

// Perform basic checks that the leaf nodes in the ratchet tree are valid
// These checks only do those that don't need group context. We do the full
// checks later, but do these here to fail early in case of funny business
treesync
.full_leaves()
.try_for_each(|leaf_node| leaf_node.validate_locally())?;

let group_info: GroupInfo = {
let signer_signature_key = treesync
.leaf(verifiable_group_info.signer())
Expand Down Expand Up @@ -175,6 +182,12 @@ impl PublicGroup {
proposal_store,
};

// Fully check that the leaf nodes in the ratchet tree are valid
public_group
.treesync
.full_leaves()
.try_for_each(|leaf_node| public_group.validate_leaf_node(leaf_node))?;

public_group
.store(storage)
.map_err(CreationFromExternalError::WriteToStorageError)?;
Expand Down
32 changes: 14 additions & 18 deletions openmls/src/group/public_group/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,13 +527,14 @@ impl PublicGroup {
};

// Make sure that all other extensions are known to be supported, by checking
// that they are included in the required capabilities.
// that they are default extensions or included in the required capabilities.
let all_extensions_are_in_required_capabilities: bool = extensions
.extensions()
.iter()
.map(|ext| ext.extension_type())
.all(|ext_type| {
required_capabilities.requires_extension_type_support(ext_type)
ext_type.is_default()
|| required_capabilities.requires_extension_type_support(ext_type)
});

if !all_extensions_are_in_required_capabilities {
Expand All @@ -557,13 +558,14 @@ impl PublicGroup {
&self,
leaf_node: &LeafNode,
) -> Result<(), LeafNodeValidationError> {
// Check that the data in the leaf node is self-consistent
leaf_node.validate_locally()?;

// Check if the ciphersuite and the version of the group are
// supported.
let capabilities = leaf_node.capabilities();
if !capabilities
.ciphersuites()
.contains(&VerifiableCiphersuite::from(self.ciphersuite()))
|| !capabilities.versions().contains(&self.version())
if !capabilities.contains_ciphersuite(VerifiableCiphersuite::from(self.ciphersuite()))
|| !capabilities.contains_version(self.version())
{
return Err(LeafNodeValidationError::CiphersuiteNotInCapabilities);
}
Expand All @@ -577,20 +579,10 @@ impl PublicGroup {
capabilities.supports_required_capabilities(required_capabilities)?;
}

// Check that all extensions are contained in the capabilities.
if !capabilities.contain_extensions(leaf_node.extensions()) {
return Err(LeafNodeValidationError::UnsupportedExtensions);
}

// Check that the capabilities contain the leaf node's credential type.
if !capabilities.contains_credential(&leaf_node.credential().credential_type()) {
return Err(LeafNodeValidationError::UnsupportedCredentials);
}

// Check that the credential type is supported by all members of the group.
if !self.treesync().full_leaves().all(|node| {
node.capabilities()
.contains_credential(&leaf_node.credential().credential_type())
.contains_credential(leaf_node.credential().credential_type())
}) {
return Err(LeafNodeValidationError::UnsupportedCredentials);
}
Expand All @@ -601,7 +593,7 @@ impl PublicGroup {
if !self
.treesync()
.full_leaves()
.all(|node| capabilities.contains_credential(&node.credential().credential_type()))
.all(|node| capabilities.contains_credential(node.credential().credential_type()))
{
return Err(LeafNodeValidationError::UnsupportedCredentials);
}
Expand All @@ -619,8 +611,12 @@ impl PublicGroup {
// 105 is done when sending

// 106
// don't enable in tests, because we are testing with kats that contain
// expired key packages
#[cfg(not(test))]
if let Some(lifetime) = leaf_node.life_time() {
if !lifetime.is_valid() {
println!("offending lifetime: {lifetime:?}");
return Err(LeafNodeValidationError::Lifetime(LifetimeError::NotCurrent));
}
}
Expand Down
6 changes: 4 additions & 2 deletions openmls/src/group/tests_and_kats/tests/proposal_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,10 +1046,12 @@ fn test_valsem105() {
for key_package_version in [
KeyPackageTestVersion::WrongCiphersuite,
KeyPackageTestVersion::WrongVersion,
KeyPackageTestVersion::UnsupportedVersion,
KeyPackageTestVersion::UnsupportedCiphersuite,
//KeyPackageTestVersion::UnsupportedVersion,
// KeyPackageTestVersion::UnsupportedCiphersuite,
KeyPackageTestVersion::ValidTestCase,
] {
println!("running test {key_package_version:?}");

// Let's set up a group with Alice and Bob as members.
let ProposalValidationTestSetup {
mut alice_group,
Expand Down
16 changes: 16 additions & 0 deletions openmls/src/messages/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ pub enum ProposalType {
Custom(u16),
}

impl ProposalType {
/// Returns true for all proposal types that are considered "default" by the spec.
pub(crate) fn is_default(self) -> bool {
match self {
ProposalType::Add
| ProposalType::Update
| ProposalType::Remove
| ProposalType::PreSharedKey
| ProposalType::Reinit
| ProposalType::ExternalInit
| ProposalType::GroupContextExtensions => true,
ProposalType::AppAck | ProposalType::Custom(_) => false,
}
}
}

impl Size for ProposalType {
fn tls_serialized_len(&self) -> usize {
2
Expand Down
43 changes: 37 additions & 6 deletions openmls/src/treesync/node/leaf_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,13 +435,14 @@ impl LeafNode {

/// Returns `true` if the [`ExtensionType`] is supported by this leaf node.
pub(crate) fn supports_extension(&self, extension_type: &ExtensionType) -> bool {
self.payload
.capabilities
.extensions
.contains(extension_type)
|| default_extensions().iter().any(|et| et == extension_type)
extension_type.is_default()
|| self
.payload
.capabilities
.extensions
.contains(extension_type)
}
///

/// Check whether the this leaf node supports all the required extensions
/// in the provided list.
pub(crate) fn check_extension_support(
Expand All @@ -455,6 +456,36 @@ impl LeafNode {
}
Ok(())
}

/// Perform all checks that can be done without further context:
/// - the used extensions are not known to be invalid in leaf leaf nodes
/// - the types of the used extensions are covered by the capabilities
/// - the type of the credential is coveered by the capabilities
pub(crate) fn validate_locally(&self) -> Result<(), LeafNodeValidationError> {
// Check that no extension is invalid when used in leaf nodes.
if self
.extensions()
.iter()
.any(|ext| ext.extension_type().is_valid_in_leaf_node() == Some(false))
{
return Err(LeafNodeValidationError::UnsupportedExtensions);
}

// Check that all extensions are contained in the capabilities.
if !self.capabilities().contains_extensions(self.extensions()) {
return Err(LeafNodeValidationError::UnsupportedExtensions);
}

// Check that the capabilities contain the leaf node's credential type.
if !self
.capabilities()
.contains_credential(self.credential().credential_type())
{
return Err(LeafNodeValidationError::UnsupportedCredentials);
}

Ok(())
}
}

/// The payload of a [`LeafNode`]
Expand Down
Loading

0 comments on commit a53a332

Please sign in to comment.