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: TestContext) {
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 .transaction
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.transaction.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 .transaction
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: TestContext) {
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 .transaction
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
122 .transaction
123 .new_add_proposal(&id, invalid_kp)
124 .await
125 .unwrap();
126 let proposal = proposal.proposal.to_bytes().unwrap();
127
128 let elapsed = start.elapsed();
129 // Give time to the certificate to expire
130 let expiration_time = core::time::Duration::from_secs(expiration_time);
131 if expiration_time > elapsed {
132 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1))
133 .await;
134 }
135
136 let decrypting = bob_central
137 .transaction
138 .conversation(&id)
139 .await
140 .unwrap()
141 .decrypt_message(proposal)
142 .await;
143
144 // 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
145 decrypting.unwrap();
146 /*assert!(matches!(
147 decrypting.unwrap_err(),
148 CryptoError::MlsError(MlsError::MlsMessageError(ProcessMessageError::ValidationError(
149 ValidationError::KeyPackageVerifyError(KeyPackageVerifyError::InvalidLeafNode(
150 LeafNodeValidationError::Lifetime(_)
151 ))
152 )))
153 ));*/
154 })
155 },
156 )
157 .await
158 }
159
160 /// The validity of a LeafNode needs to be verified at the following stages:
161 /// When a LeafNode is received by a group member in an Add, Update, or Commit message
162 #[apply(all_cred_cipher)]
163 #[wasm_bindgen_test]
164 async fn should_validate_leaf_node_when_receiving_add_commit(case: TestContext) {
165 run_test_with_client_ids(
166 case.clone(),
167 ["alice", "bob", "charlie"],
168 move |[alice_central, bob_central, charlie_central]| {
169 Box::pin(async move {
170 let expiration_time = 14;
171 let start = web_time::Instant::now();
172 let id = conversation_id();
173 alice_central
174 .transaction
175 .new_conversation(&id, case.credential_type, case.cfg.clone())
176 .await
177 .unwrap();
178 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
179
180 // should fail when receiving Add commit
181 let invalid_kp = charlie_central
182 .new_keypackage(&case, Lifetime::new(expiration_time))
183 .await;
184
185 alice_central
186 .transaction
187 .conversation(&id)
188 .await
189 .unwrap()
190 .add_members(vec![invalid_kp.into()])
191 .await
192 .unwrap();
193 let commit = alice_central.mls_transport.latest_commit().await;
194 let commit = commit.to_bytes().unwrap();
195
196 let elapsed = start.elapsed();
197 // Give time to the certificate to expire
198 let expiration_time = core::time::Duration::from_secs(expiration_time);
199 if expiration_time > elapsed {
200 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1))
201 .await;
202 }
203
204 let decrypting = bob_central
205 .transaction
206 .conversation(&id)
207 .await
208 .unwrap()
209 .decrypt_message(commit)
210 .await;
211
212 // 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
213 decrypting.unwrap();
214 /*assert!(matches!(
215 decrypting.unwrap_err(),
216 CryptoError::MlsError(MlsError::MlsMessageError(ProcessMessageError::ValidationError(
217 ValidationError::KeyPackageVerifyError(KeyPackageVerifyError::InvalidLeafNode(_))
218 )))
219 ));*/
220 })
221 },
222 )
223 .await
224 }
225
226 /// The validity of a LeafNode needs to be verified at the following stages:
227 /// When a client validates a ratchet tree, e.g., when joining a group or after processing a Commit
228 #[apply(all_cred_cipher)]
229 #[wasm_bindgen_test]
230 async fn should_validate_leaf_node_when_receiving_welcome(case: TestContext) {
231 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
232 Box::pin(async move {
233 let expiration_time = 14;
234 let start = web_time::Instant::now();
235 let id = conversation_id();
236 alice_central
237 .transaction
238 .new_conversation(&id, case.credential_type, case.cfg.clone())
239 .await
240 .unwrap();
241
242 let invalid_kp = bob_central.new_keypackage(&case, Lifetime::new(expiration_time)).await;
243 alice_central
244 .transaction
245 .conversation(&id)
246 .await
247 .unwrap()
248 .add_members(vec![invalid_kp.into()])
249 .await
250 .unwrap();
251
252 let elapsed = start.elapsed();
253 // Give time to the certificate to expire
254 let expiration_time = core::time::Duration::from_secs(expiration_time);
255 if expiration_time > elapsed {
256 async_std::task::sleep(expiration_time - elapsed + core::time::Duration::from_secs(1)).await;
257 }
258
259 let process_welcome = bob_central
260 .transaction
261 .process_welcome_message(
262 alice_central.mls_transport.latest_welcome_message().await.into(),
263 case.custom_cfg(),
264 )
265 .await;
266
267 // 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
268 process_welcome.unwrap();
269 /*assert!(matches!(
270 process_welcome.unwrap_err(),
271 CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::PublicGroupError(
272 CreationFromExternalError::TreeSyncError(
273 TreeSyncFromNodesError::LeafNodeValidationError(LeafNodeValidationError::Lifetime(
274 _
275 ))
276 )
277 )))
278 ));*/
279 })
280 })
281 .await
282 }
283 }
284}