// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright by contributors to this project. // SPDX-License-Identifier: (Apache-2.0 OR MIT)
use alloc::vec; use alloc::vec::Vec; #[cfg(feature = "std")] use core::fmt::Display; use itertools::Itertools; use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize}; use mls_rs_core::extension::ExtensionList;
use mls_rs_core::{error::IntoAnyError, identity::IdentityProvider};
#[cfg(feature = "tree_index")] use mls_rs_core::identity::SigningIdentity;
use math as tree_math; use node::{LeafIndex, NodeIndex, NodeVec};
mod capabilities; pub(crate) mod hpke_encryption; mod lifetime; pub(crate) mod math; pubmod node; pubmod parent_hash; pubmod path_secret; mod private; mod tree_hash; pubmod tree_validator; pubmod update_path;
pubuse capabilities::*; pubuse lifetime::*; pub(crate) use private::*; pubuse update_path::*;
use tree_index::*;
pubmod kem; pubmod leaf_node; pubmod leaf_node_validator; mod tree_index;
#[cfg(feature = "std")] pub(crate) mod tree_utils;
// Verify the parent hash of the new sender leaf node and update the parent hash values // in the local tree self.update_parent_hashes(sender, true, cipher_suite_provider)
.await?;
Ok(())
}
fn update_unmerged(&mutself, index: LeafIndex) -> Result<(), MlsError> { // For a given leaf index, find parent nodes and add the leaf to the unmerged leaf self.nodes.direct_copath(index).into_iter().for_each(|i| { iflet Ok(p) = self.nodes.borrow_as_parent_mut(i.path) {
p.unmerged_leaves.push(index)
}
});
Ok(())
}
#[cfg(feature = "by_ref_proposal")] #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] pubasyncfn batch_edit<I, CP>(
&mutself,
proposal_bundle: &mut ProposalBundle,
extensions: &ExtensionList,
id_provider: &I,
cipher_suite_provider: &CP,
filter: bool,
) -> Result<Vec<LeafIndex>, MlsError> where
I: IdentityProvider,
CP: CipherSuiteProvider,
{ // Apply removes (they commute with updates because they don't touch the same leaves) for i in (0..proposal_bundle.remove_proposals().len()).rev() { let index = proposal_bundle.remove_proposals()[i].proposal.to_remove; let res = self.nodes.blank_leaf_node(index);
if res.is_ok() { // This shouldn't fail if `blank_leaf_node` succedded. self.nodes.blank_direct_path(index)?;
}
#[cfg(feature = "tree_index")] iflet Ok(old_leaf) = &res { // If this fails, it's not because the proposal is bad. let identity =
identity(&old_leaf.signing_identity, id_provider, extensions).await?;
// Apply updates one by one. If there's an update which we can't apply or revert, we revert // all updates. for (index, old_leaf, new_leaf, i) in partial_updates.into_iter() { #[cfg(feature = "tree_index")] let res =
index_insert(&mutself.index, &new_leaf, index, id_provider, extensions).await;
#[cfg(not(feature = "tree_index"))] let res = index_insert(&self.nodes, &new_leaf, index, id_provider, extensions).await;
let err = res.is_err();
if !filter {
res?;
}
if !err { self.nodes.insert_leaf(index, new_leaf);
removed_leaves.push(old_leaf);
updated_indices.push(index);
} else { #[cfg(feature = "tree_index")] let res =
index_insert(&mutself.index, &old_leaf, index, id_provider, extensions).await;
#[cfg(not(feature = "tree_index"))] let res =
index_insert(&self.nodes, &old_leaf, index, id_provider, extensions).await;
if res.is_ok() { self.nodes.insert_leaf(index, old_leaf);
bad_indices.push(i);
} else { // Revert all updates and stop. We're already in the "filter" case, so we don't throw an error. #[cfg(feature = "tree_index")]
{ self.index = index_clone;
}
// If we managed to update something, blank direct paths
updated_indices
.iter()
.try_for_each(|index| self.nodes.blank_direct_path(*index).map(|_| ()))?;
// Remove rejected updates from applied proposals if updated_indices.is_empty() { // This takes care of the "revert all" scenario
proposal_bundle.updates = vec![];
} else { for i in bad_indices.into_iter().rev() {
proposal_bundle.remove::<UpdateProposal>(i);
proposal_bundle.update_senders.remove(i);
}
}
for p in &proposal_bundle.additions { let leaf = p.proposal.key_package.leaf_node.clone();
start = self
.add_leaf(leaf, id_provider, extensions, Some(start))
.await?;
added.push(start);
}
self.nodes.trim();
let updated_leaves = proposal_bundle
.remove_proposals()
.iter()
.map(|p| p.proposal.to_remove)
.chain(added.iter().copied())
.collect_vec();
#[cfg(test)] pub(crate) mod test_utils { usecrate::crypto::test_utils::TestCryptoProvider; usecrate::signer::Signable; use alloc::vec::Vec; use alloc::{format, vec}; use mls_rs_core::crypto::CipherSuiteProvider; use mls_rs_core::group::Capabilities; use mls_rs_core::identity::BasicCredential;
// A adds B, B adds C, C adds D etc. for i in1..n_leaves {
tree.add_member(&format!("Alice{i}"), cs).await;
tree.update_committer_path(i - 1, cs).await;
}
tree
}
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] pubasyncfn add_member<P: CipherSuiteProvider>(&mutself, name: &str, cs: &P) { let (leaf, signer) = make_leaf(name, cs).await; let index = self.tree.nodes.next_empty_leaf(LeafIndex(0)); self.tree.nodes.insert_leaf(index, leaf); self.tree.update_unmerged(index).unwrap(); let index = *index as usize;
matchself.signers.len() {
l if l == index => self.signers.push(Some(signer)),
l if l > index => self.signers[index] = Some(signer),
_ => panic!("signer tree size mismatch"),
}
}
usecrate::identity::basic::BasicIdentityProvider; usecrate::tree_kem::leaf_node::LeafNode; usecrate::tree_kem::node::{LeafIndex, Node, NodeIndex, NodeTypeResolver, Parent}; usecrate::tree_kem::parent_hash::ParentHash; usecrate::tree_kem::test_utils::{get_test_leaf_nodes, get_test_tree}; usecrate::tree_kem::{MlsError, TreeKemPublic}; use alloc::borrow::ToOwned; use alloc::vec; use alloc::vec::Vec; use assert_matches::assert_matches;
#[cfg(feature = "by_ref_proposal")] use alloc::boxed::Box;
#[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] asyncfn test_derive() { for cipher_suite in TestCryptoProvider::all_supported_cipher_suites() { let test_tree = get_test_tree(cipher_suite).await;
#[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] asyncfn test_add_leaf() { let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE); letmut tree = TreeKemPublic::new();
let leaf_nodes = get_test_leaf_nodes(TEST_CIPHER_SUITE).await;
let res = tree
.add_leaves(
leaf_nodes.clone(),
&BasicIdentityProvider,
&cipher_suite_provider,
)
.await
.unwrap();
// The leaf count should be equal to the number of packages we added
assert_eq!(res.len(), leaf_nodes.len());
assert_eq!(tree.occupied_leaf_count(), leaf_nodes.len() as u32);
// Each added package should be at the proper index and searchable in the tree
res.into_iter().zip(leaf_nodes.clone()).for_each(|(r, kp)| {
assert_eq!(tree.get_leaf_node(r).unwrap(), &kp);
});
// Verify the underlying state #[cfg(feature = "tree_index")]
assert_eq!(tree.index.len(), tree.occupied_leaf_count() as usize);
tree.nodes[0] = None; // Set the original first node to none //
tree.add_leaves(
[key_packages[1].clone()].to_vec(),
&BasicIdentityProvider,
&cipher_suite_provider,
)
.await
.unwrap();
// Add in parent nodes so we can detect them clearing after update
tree.nodes.direct_copath(LeafIndex(0)).iter().for_each(|n| {
tree.nodes
.borrow_or_fill_node_as_parent(n.path, &b"pub_key".to_vec().into())
.unwrap();
});
let original_size = tree.occupied_leaf_count(); let original_leaf_index = LeafIndex(1);
let updated_leaf = get_basic_test_node(TEST_CIPHER_SUITE, "A").await;
// The tree should not have grown due to an update
assert_eq!(tree.occupied_leaf_count(), original_size);
// The cache of tree package indexes should not have grown #[cfg(feature = "tree_index")]
assert_eq!(tree.index.len() as u32, tree.occupied_leaf_count());
// The key package should be updated in the tree
assert_eq!(
tree.get_leaf_node(original_leaf_index).unwrap(),
&updated_leaf
);
// Verify that the direct path has been cleared
tree.nodes.direct_copath(LeafIndex(0)).iter().for_each(|n| {
assert!(tree.nodes[n.path as usize].is_none());
});
}
#[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] asyncfn test_find_leaf_node() { let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE); // Create a tree letmut tree = get_test_tree(TEST_CIPHER_SUITE).await.public;
let leaf_nodes = get_test_leaf_nodes(TEST_CIPHER_SUITE).await;
// Find each node for (i, leaf_node) in leaf_nodes.iter().enumerate() { let expected_index = LeafIndex(i as u32 + 1);
assert_eq!(tree.find_leaf_node(leaf_node), Some(expected_index));
}
}
// TODO add test for the lite version #[cfg(feature = "by_ref_proposal")] #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] asyncfn batch_edit_works() { let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
letmut tree = get_test_tree(TEST_CIPHER_SUITE).await.public; let leaf_nodes = get_test_leaf_nodes(TEST_CIPHER_SUITE).await;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.