core_crypto/mls/conversation/leaf_node_validation.rs
1//! cf <https://www.rfc-editor.org/rfc/rfc9420.html#name-leaf-node-validation>
2
3#[cfg(test)]
4mod tests {
5 use openmls::prelude::Lifetime;
6 use wasm_bindgen_test::*;
7
8 use crate::{MlsErrorKind, test_utils::*};
9
10 use openmls::prelude::{AddMembersError, KeyPackageVerifyError};
11
12 wasm_bindgen_test_configure!(run_in_browser);
13
14 mod stages {
15
16 use super::*;
17
18 /// The validity of a LeafNode needs to be verified at the following stages:
19 /// When a LeafNode is downloaded in a KeyPackage, before it is used to add the client to the group
20 #[apply(all_cred_cipher)]
21 #[wasm_bindgen_test]
22 async fn should_validate_leaf_node_when_adding(case: TestCase) {
23 run_test_with_client_ids(
24 case.clone(),
25 ["alice", "bob"],
26 move |[mut alice_central, bob_central]| {
27 Box::pin(async move {
28 let expiration_time = 14;
29 let start = web_time::Instant::now();
30 let id = conversation_id();
31 alice_central
32 .context
33 .new_conversation(&id, case.credential_type, case.cfg.clone())
34 .await
35 .unwrap();
36
37 // should fail when creating Add proposal
38 let invalid_kp = bob_central.new_keypackage(&case, Lifetime::new(expiration_time)).await;
39
40 // Give time to the KeyPackage to expire
41 let expiration_time = core::time::Duration::from_secs(expiration_time);
42 let elapsed = start.elapsed();
43 if expiration_time > elapsed {
44 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1))
45 .await;
46 }
47
48 let proposal_creation = alice_central.context.new_add_proposal(&id, invalid_kp).await;
49 let error = proposal_creation.unwrap_err();
50 assert!(innermost_source_matches!(
51 error,
52 MlsErrorKind::ProposeAddMemberError(
53 openmls::prelude::ProposeAddMemberError::KeyPackageVerifyError(
54 KeyPackageVerifyError::InvalidLeafNode(_)
55 )
56 ),
57 ));
58 assert!(alice_central.pending_proposals(&id).await.is_empty());
59
60 // should fail when creating Add commits
61 let expiration_time = 14;
62 let start = web_time::Instant::now();
63
64 let invalid_kp = bob_central.new_keypackage(&case, Lifetime::new(expiration_time)).await;
65
66 // Give time to the KeyPackage to expire
67 let expiration_time = core::time::Duration::from_secs(expiration_time);
68 let elapsed = start.elapsed();
69 if expiration_time > elapsed {
70 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1))
71 .await;
72 }
73
74 let commit_creation = alice_central
75 .context
76 .conversation(&id)
77 .await
78 .unwrap()
79 .add_members(vec![invalid_kp.into()])
80 .await;
81
82 let error = commit_creation.unwrap_err();
83 assert!(innermost_source_matches!(
84 error,
85 MlsErrorKind::MlsAddMembersError(AddMembersError::KeyPackageVerifyError(
86 KeyPackageVerifyError::InvalidLeafNode(_)
87 )),
88 ));
89 assert!(alice_central.pending_proposals(&id).await.is_empty());
90 assert!(alice_central.pending_commit(&id).await.is_none());
91 })
92 },
93 )
94 .await
95 }
96
97 /// The validity of a LeafNode needs to be verified at the following stages:
98 /// When a LeafNode is received by a group member in an Add, Update, or Commit message
99 #[apply(all_cred_cipher)]
100 #[wasm_bindgen_test]
101 async fn should_validate_leaf_node_when_receiving_expired_add_proposal(case: TestCase) {
102 run_test_with_client_ids(
103 case.clone(),
104 ["alice", "bob", "charlie"],
105 move |[alice_central, bob_central, charlie_central]| {
106 Box::pin(async move {
107 let expiration_time = 14;
108 let start = web_time::Instant::now();
109 let id = conversation_id();
110 alice_central
111 .context
112 .new_conversation(&id, case.credential_type, case.cfg.clone())
113 .await
114 .unwrap();
115 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
116
117 let invalid_kp = charlie_central
118 .new_keypackage(&case, Lifetime::new(expiration_time))
119 .await;
120
121 let proposal = alice_central.context.new_add_proposal(&id, invalid_kp).await.unwrap();
122 let proposal = proposal.proposal.to_bytes().unwrap();
123
124 let elapsed = start.elapsed();
125 // Give time to the certificate to expire
126 let expiration_time = core::time::Duration::from_secs(expiration_time);
127 if expiration_time > elapsed {
128 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1))
129 .await;
130 }
131
132 let decrypting = bob_central
133 .context
134 .conversation(&id)
135 .await
136 .unwrap()
137 .decrypt_message(proposal)
138 .await;
139
140 // TODO: currently succeeds as we don't anymore validate KeyPackage lifetime upon reception: find another way to craft an invalid KeyPackage. Tracking issue number: WPB-9623
141 decrypting.unwrap();
142 /*assert!(matches!(
143 decrypting.unwrap_err(),
144 CryptoError::MlsError(MlsError::MlsMessageError(ProcessMessageError::ValidationError(
145 ValidationError::KeyPackageVerifyError(KeyPackageVerifyError::InvalidLeafNode(
146 LeafNodeValidationError::Lifetime(_)
147 ))
148 )))
149 ));*/
150 })
151 },
152 )
153 .await
154 }
155
156 /// The validity of a LeafNode needs to be verified at the following stages:
157 /// When a LeafNode is received by a group member in an Add, Update, or Commit message
158 #[apply(all_cred_cipher)]
159 #[wasm_bindgen_test]
160 async fn should_validate_leaf_node_when_receiving_add_commit(case: TestCase) {
161 run_test_with_client_ids(
162 case.clone(),
163 ["alice", "bob", "charlie"],
164 move |[alice_central, bob_central, charlie_central]| {
165 Box::pin(async move {
166 let expiration_time = 14;
167 let start = web_time::Instant::now();
168 let id = conversation_id();
169 alice_central
170 .context
171 .new_conversation(&id, case.credential_type, case.cfg.clone())
172 .await
173 .unwrap();
174 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
175
176 // should fail when receiving Add commit
177 let invalid_kp = charlie_central
178 .new_keypackage(&case, Lifetime::new(expiration_time))
179 .await;
180
181 alice_central
182 .context
183 .conversation(&id)
184 .await
185 .unwrap()
186 .add_members(vec![invalid_kp.into()])
187 .await
188 .unwrap();
189 let commit = alice_central.mls_transport.latest_commit().await;
190 let commit = commit.to_bytes().unwrap();
191
192 let elapsed = start.elapsed();
193 // Give time to the certificate to expire
194 let expiration_time = core::time::Duration::from_secs(expiration_time);
195 if expiration_time > elapsed {
196 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1))
197 .await;
198 }
199
200 let decrypting = bob_central
201 .context
202 .conversation(&id)
203 .await
204 .unwrap()
205 .decrypt_message(commit)
206 .await;
207
208 // TODO: currently succeeds as we don't anymore validate KeyPackage lifetime upon reception: find another way to craft an invalid KeyPackage. Tracking issue number: WPB-9623
209 decrypting.unwrap();
210 /*assert!(matches!(
211 decrypting.unwrap_err(),
212 CryptoError::MlsError(MlsError::MlsMessageError(ProcessMessageError::ValidationError(
213 ValidationError::KeyPackageVerifyError(KeyPackageVerifyError::InvalidLeafNode(_))
214 )))
215 ));*/
216 })
217 },
218 )
219 .await
220 }
221
222 /// The validity of a LeafNode needs to be verified at the following stages:
223 /// When a client validates a ratchet tree, e.g., when joining a group or after processing a Commit
224 #[apply(all_cred_cipher)]
225 #[wasm_bindgen_test]
226 async fn should_validate_leaf_node_when_receiving_welcome(case: TestCase) {
227 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
228 Box::pin(async move {
229 let expiration_time = 14;
230 let start = web_time::Instant::now();
231 let id = conversation_id();
232 alice_central
233 .context
234 .new_conversation(&id, case.credential_type, case.cfg.clone())
235 .await
236 .unwrap();
237
238 let invalid_kp = bob_central.new_keypackage(&case, Lifetime::new(expiration_time)).await;
239 alice_central
240 .context
241 .conversation(&id)
242 .await
243 .unwrap()
244 .add_members(vec![invalid_kp.into()])
245 .await
246 .unwrap();
247
248 let elapsed = start.elapsed();
249 // Give time to the certificate to expire
250 let expiration_time = core::time::Duration::from_secs(expiration_time);
251 if expiration_time > elapsed {
252 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1)).await;
253 }
254
255 let process_welcome = bob_central
256 .context
257 .process_welcome_message(
258 alice_central.mls_transport.latest_welcome_message().await.into(),
259 case.custom_cfg(),
260 )
261 .await;
262
263 // TODO: currently succeeds as we don't anymore validate KeyPackage lifetime upon reception: find another way to craft an invalid KeyPackage. Tracking issue number: WPB-9623
264 process_welcome.unwrap();
265 /*assert!(matches!(
266 process_welcome.unwrap_err(),
267 CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::PublicGroupError(
268 CreationFromExternalError::TreeSyncError(
269 TreeSyncFromNodesError::LeafNodeValidationError(LeafNodeValidationError::Lifetime(
270 _
271 ))
272 )
273 )))
274 ));*/
275 })
276 })
277 .await
278 }
279 }
280}