Skip to content

Commit 76b5ea2

Browse files
franziskuskieferneekolas
authored andcommitted
fix: ratchet tree leaf node lifetime validation
Do not validate the lifetime in leaf nodes in the ratchet tree when joining a group.
1 parent 36cc6d1 commit 76b5ea2

6 files changed

Lines changed: 151 additions & 4 deletions

File tree

openmls/src/group/public_group/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ impl PublicGroup {
278278
public_group
279279
.treesync
280280
.full_leaves()
281-
.try_for_each(|leaf_node| public_group.validate_leaf_node(leaf_node))?;
281+
.try_for_each(|leaf_node| public_group.validate_leaf_node_inner(leaf_node, true))?;
282282

283283
Ok((public_group, group_info))
284284
}

openmls/src/group/public_group/validation.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,24 @@ impl PublicGroup {
649649
Ok(())
650650
}
651651

652+
/// Validate a leaf node.
653+
///
654+
/// This always validates the lifetime.
652655
pub(crate) fn validate_leaf_node(
653656
&self,
654657
leaf_node: &crate::treesync::LeafNode,
658+
) -> Result<(), LeafNodeValidationError> {
659+
// Call the validation function and validate the lifetime
660+
self.validate_leaf_node_inner(leaf_node, false)
661+
}
662+
663+
/// Validate a leaf node.
664+
///
665+
/// This may skip checking the lifetime when validating a ratchet tree.
666+
pub(crate) fn validate_leaf_node_inner(
667+
&self,
668+
leaf_node: &crate::treesync::LeafNode,
669+
ratchet_tree: bool,
655670
) -> Result<(), LeafNodeValidationError> {
656671
// https://validation.openmls.tech/#valn0103
657672
// https://validation.openmls.tech/#valn0104
@@ -665,9 +680,18 @@ impl PublicGroup {
665680
// Only leaf nodes in key packages contain lifetimes, so this will return None for other
666681
// cases. Therefore we only check the lifetimes for leaf nodes in key packages.
667682
//
683+
// We may want to check these in ratchet trees as well.
684+
// However, this may lead to errors when leaf nodes don't get updated
685+
// after being added to the tree. RFC 9420 recommends checking the lifetime
686+
// but acknowledges already that this may cause issues.
687+
// https://www.rfc-editor.org/rfc/rfc9420.html#section-7.3-4.5.1
688+
// See #1810 for more background.
689+
// We therefore DO NOT check the lifetime if this is a leaf node check
690+
// for a ratchet tree.
691+
//
668692
// Some KATs use key packages that are expired by now. In order to run these tests, we
669693
// provide a way to turn off this check.
670-
if !crate::skip_validation::is_disabled::leaf_node_lifetime() {
694+
if !ratchet_tree && !crate::skip_validation::is_disabled::leaf_node_lifetime() {
671695
if let Some(lifetime) = leaf_node.life_time() {
672696
if !lifetime.is_valid() {
673697
log::warn!("offending lifetime: {lifetime:?}");

openmls/src/group/tests_and_kats/tests/capabilities_check.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ impl<'a, 'b: 'a, Provider: OpenMlsProvider> PreGroupPartyState<'b, Provider> {
154154
},
155155
Extensions::default(),
156156
&self.core_state.provider,
157+
None,
157158
&self.signer,
158159
);
159160
}

openmls/src/key_packages/lifetime.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ impl Lifetime {
7373
}
7474
}
7575

76+
/// Initialize raw lifetime without skew and explicit dates.
77+
pub fn init(not_before: u64, not_after: u64) -> Self {
78+
Self {
79+
not_before,
80+
not_after,
81+
}
82+
}
83+
7684
/// Returns true if this lifetime is valid.
7785
pub fn is_valid(&self) -> bool {
7886
match SystemTime::now()

openmls/src/test_utils/single_group_test_framework/mod.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,16 @@ pub(crate) fn generate_key_package(
5151
credential_with_key: CredentialWithKey,
5252
extensions: Extensions,
5353
provider: &impl crate::storage::OpenMlsProvider,
54+
lifetime: impl Into<Option<Lifetime>>,
5455
signer: &impl Signer,
5556
) -> KeyPackageBundle {
56-
KeyPackage::builder()
57-
.key_package_extensions(extensions)
57+
let mut builder = KeyPackage::builder().key_package_extensions(extensions);
58+
59+
if let Some(lifetime) = lifetime.into() {
60+
builder = builder.key_package_lifetime(lifetime);
61+
}
62+
63+
builder
5864
.build(ciphersuite, provider, signer, credential_with_key)
5965
.unwrap()
6066
}
@@ -83,9 +89,19 @@ pub struct PreGroupPartyState<'a, Provider> {
8389
pub core_state: &'a CorePartyState<Provider>,
8490
}
8591

92+
// XXX: This should probably get a builder at some point.
8693
impl<Provider: OpenMlsProvider> CorePartyState<Provider> {
8794
/// Generates the pre-group state for a `CorePartyState`
8895
pub fn generate_pre_group(&self, ciphersuite: Ciphersuite) -> PreGroupPartyState<'_, Provider> {
96+
self.generate_pre_group_lifetime(ciphersuite, None)
97+
}
98+
99+
/// Generates the pre-group state for a `CorePartyState`
100+
pub fn generate_pre_group_lifetime(
101+
&self,
102+
ciphersuite: Ciphersuite,
103+
lifetime: impl Into<Option<Lifetime>>,
104+
) -> PreGroupPartyState<'_, Provider> {
89105
let (credential_with_key, signer) = generate_credential(
90106
self.name.into(),
91107
ciphersuite.signature_algorithm(),
@@ -97,6 +113,7 @@ impl<Provider: OpenMlsProvider> CorePartyState<Provider> {
97113
credential_with_key.clone(),
98114
Extensions::default(), // TODO: provide as argument?
99115
&self.provider,
116+
lifetime,
100117
&signer,
101118
);
102119

openmls/tests/join.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use std::{
2+
thread,
3+
time::{Duration, SystemTime, UNIX_EPOCH},
4+
};
5+
6+
use openmls::{prelude::*, test_utils::single_group_test_framework::*};
7+
use openmls_test::openmls_test;
8+
9+
#[openmls_test]
10+
fn join_tree_with_outdated_leafnodes() {
11+
// The validity of the key package into the future.
12+
// This has to be short to keep the test run fast, but not too short to produce
13+
// failing tests.
14+
const VALIDITY: u64 = 2;
15+
16+
let alice_party = CorePartyState::<Provider>::new("alice");
17+
let bob_party = CorePartyState::<Provider>::new("bob");
18+
let charlie_party = CorePartyState::<Provider>::new("charlie");
19+
20+
// Create group
21+
let create_config = MlsGroupCreateConfig::test_default_from_ciphersuite(ciphersuite);
22+
let join_config = create_config.join_config().clone();
23+
let mut group_state = {
24+
let group_id = GroupId::from_slice(b"Test Group");
25+
26+
let group_state = GroupState::new_from_party(
27+
group_id,
28+
alice_party.generate_pre_group(ciphersuite),
29+
create_config,
30+
)
31+
.unwrap();
32+
group_state
33+
};
34+
35+
// Create Charlie key package
36+
let charlie_pre_group = charlie_party.generate_pre_group(ciphersuite);
37+
let charlie_key_package = charlie_pre_group.key_package_bundle.key_package().clone();
38+
39+
// Generate a key package for Bob that is outdated when inviting Charlie.
40+
// This test assumes that the setup goes through within VALIDITY seconds.
41+
// After that the key package is invalid already.
42+
let now = SystemTime::now()
43+
.duration_since(UNIX_EPOCH)
44+
.expect("SystemTime before UNIX EPOCH!")
45+
.as_secs();
46+
let bob_pre_group = bob_party
47+
.generate_pre_group_lifetime(ciphersuite, Lifetime::init(now - 60, now + VALIDITY));
48+
let bob_key_package = bob_pre_group.key_package_bundle.key_package().clone();
49+
50+
let [alice] = group_state.members_mut(&["alice"]);
51+
52+
// Alice adds Bob
53+
let (_mls_message_out, _welcome, _group_info) = alice
54+
.group
55+
.add_members(
56+
&alice_party.provider,
57+
&alice.party.signer,
58+
&[bob_key_package],
59+
)
60+
.expect("Could not add Bob.");
61+
62+
alice
63+
.group
64+
.merge_pending_commit(&alice_party.provider)
65+
.unwrap();
66+
67+
// We don't care about Bob actually processing the Welcome.
68+
// Let's wait for VALIDITY seconds to ensure that the leaf node is invalid.
69+
thread::sleep(Duration::from_secs(VALIDITY));
70+
71+
// Alice adds Charlie
72+
// At this point Bob's key package is outdated in the tree.
73+
let (_mls_message_out, welcome, _group_info) = alice
74+
.group
75+
.add_members(
76+
&alice_party.provider,
77+
&alice.party.signer,
78+
&[charlie_key_package],
79+
)
80+
.expect("Could not add Charlie.");
81+
82+
alice
83+
.group
84+
.merge_pending_commit(&alice_party.provider)
85+
.unwrap();
86+
87+
// Charlie tries to join the group
88+
let _charlie_group = StagedWelcome::new_from_welcome(
89+
&charlie_party.provider,
90+
&join_config,
91+
MlsMessageIn::from(welcome).into_welcome().unwrap(),
92+
None,
93+
)
94+
.expect("Failed to create group due to an invalid lifetime in a leaf node in the tree.")
95+
.into_group(provider)
96+
.unwrap();
97+
}

0 commit comments

Comments
 (0)