core_crypto/mls/conversation/
duplicate.rs1use super::{Error, Result};
6use crate::{MlsError, prelude::MlsConversation};
7use mls_crypto_provider::MlsCryptoProvider;
8use openmls::prelude::{ContentType, FramedContentBodyIn, Proposal, PublicMessageIn, Sender};
9
10impl MlsConversation {
11 pub(crate) fn is_duplicate_message(&self, backend: &MlsCryptoProvider, msg: &PublicMessageIn) -> Result<bool> {
12 let (sender, content_type) = (msg.sender(), msg.body().content_type());
13
14 match (content_type, sender) {
15 (ContentType::Commit, Sender::Member(_) | Sender::NewMemberCommit) => {
16 if let Some(msg_ct) = msg.confirmation_tag() {
19 let group_ct = self
20 .group
21 .compute_confirmation_tag(backend)
22 .map_err(MlsError::wrap("computing confirmation tag"))?;
23 Ok(msg_ct == &group_ct)
24 } else {
25 Err(Error::MlsGroupInvalidState("a commit must have a ConfirmationTag"))
27 }
28 }
29 (ContentType::Proposal, Sender::Member(_) | Sender::NewMemberProposal) => {
30 match msg.body() {
31 FramedContentBodyIn::Proposal(proposal) => {
32 let proposal = Proposal::from(proposal.clone()); let already_exists = self.group.pending_proposals().any(|pp| pp.proposal() == &proposal);
34 Ok(already_exists)
35 }
36 _ => Err(Error::MlsGroupInvalidState(
37 "message body was not a proposal despite ContentType::Proposal",
38 )),
39 }
40 }
41 (_, _) => Ok(false),
42 }
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::super::error::Error;
49 use crate::mls::conversation::Conversation as _;
50 use crate::test_utils::*;
51 use wasm_bindgen_test::*;
52
53 wasm_bindgen_test_configure!(run_in_browser);
54
55 #[apply(all_cred_cipher)]
56 #[wasm_bindgen_test]
57 async fn decrypting_duplicate_member_commit_should_fail(case: TestContext) {
58 if !case.is_pure_ciphertext() {
60 let [alice_central, bob_central] = case.sessions().await;
61 Box::pin(async move {
62 let id = conversation_id();
63 alice_central
64 .transaction
65 .new_conversation(&id, case.credential_type, case.cfg.clone())
66 .await
67 .unwrap();
68 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
69
70 let unknown_commit = alice_central.create_unmerged_commit(&id).await.commit;
72 alice_central
73 .transaction
74 .conversation(&id)
75 .await
76 .unwrap()
77 .clear_pending_commit()
78 .await
79 .unwrap();
80
81 alice_central
82 .transaction
83 .conversation(&id)
84 .await
85 .unwrap()
86 .update_key_material()
87 .await
88 .unwrap();
89 let commit = alice_central.mls_transport().await.latest_commit().await;
90
91 bob_central
93 .transaction
94 .conversation(&id)
95 .await
96 .unwrap()
97 .decrypt_message(&commit.to_bytes().unwrap())
98 .await
99 .unwrap();
100 let decrypt_duplicate = bob_central
102 .transaction
103 .conversation(&id)
104 .await
105 .unwrap()
106 .decrypt_message(&commit.to_bytes().unwrap())
107 .await;
108 assert!(matches!(decrypt_duplicate.unwrap_err(), Error::DuplicateMessage));
109
110 let decrypt_lost_commit = bob_central
113 .transaction
114 .conversation(&id)
115 .await
116 .unwrap()
117 .decrypt_message(&unknown_commit.to_bytes().unwrap())
118 .await;
119 assert!(matches!(decrypt_lost_commit.unwrap_err(), Error::StaleCommit));
120 })
121 .await
122 }
123 }
124
125 #[apply(all_cred_cipher)]
126 #[wasm_bindgen_test]
127 async fn decrypting_duplicate_external_commit_should_fail(case: TestContext) {
128 let [alice_central, bob_central] = case.sessions().await;
129 Box::pin(async move {
130 let id = conversation_id();
131 alice_central
132 .transaction
133 .new_conversation(&id, case.credential_type, case.cfg.clone())
134 .await
135 .unwrap();
136
137 let gi = alice_central.get_group_info(&id).await;
138
139 let (unknown_ext_commit, mut pending_conversation) = bob_central
141 .create_unmerged_external_commit(gi.clone(), case.custom_cfg(), case.credential_type)
142 .await;
143 let unknown_ext_commit = unknown_ext_commit.commit;
144 pending_conversation.clear().await.unwrap();
145
146 bob_central
147 .transaction
148 .join_by_external_commit(gi, case.custom_cfg(), case.credential_type)
149 .await
150 .unwrap();
151 let ext_commit = bob_central.mls_transport().await.latest_commit().await;
152
153 alice_central
155 .transaction
156 .conversation(&id)
157 .await
158 .unwrap()
159 .decrypt_message(&ext_commit.to_bytes().unwrap())
160 .await
161 .unwrap();
162 let decryption = alice_central
164 .transaction
165 .conversation(&id)
166 .await
167 .unwrap()
168 .decrypt_message(&ext_commit.to_bytes().unwrap())
169 .await;
170 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
171
172 let decryption = alice_central
175 .transaction
176 .conversation(&id)
177 .await
178 .unwrap()
179 .decrypt_message(&unknown_ext_commit.to_bytes().unwrap())
180 .await;
181 assert!(matches!(decryption.unwrap_err(), Error::StaleCommit));
182 })
183 .await
184 }
185
186 #[apply(all_cred_cipher)]
187 #[wasm_bindgen_test]
188 async fn decrypting_duplicate_proposal_should_fail(case: TestContext) {
189 let [alice_central, bob_central] = case.sessions().await;
190 Box::pin(async move {
191 let id = conversation_id();
192 alice_central
193 .transaction
194 .new_conversation(&id, case.credential_type, case.cfg.clone())
195 .await
196 .unwrap();
197 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
198
199 let proposal = alice_central
200 .transaction
201 .new_update_proposal(&id)
202 .await
203 .unwrap()
204 .proposal;
205
206 bob_central
208 .transaction
209 .conversation(&id)
210 .await
211 .unwrap()
212 .decrypt_message(&proposal.to_bytes().unwrap())
213 .await
214 .unwrap();
215
216 let decryption = bob_central
218 .transaction
219 .conversation(&id)
220 .await
221 .unwrap()
222 .decrypt_message(&proposal.to_bytes().unwrap())
223 .await;
224 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
225
226 bob_central
228 .transaction
229 .conversation(&id)
230 .await
231 .unwrap()
232 .commit_pending_proposals()
233 .await
234 .unwrap();
235
236 let decryption = bob_central
238 .transaction
239 .conversation(&id)
240 .await
241 .unwrap()
242 .decrypt_message(&proposal.to_bytes().unwrap())
243 .await;
244 assert!(matches!(decryption.unwrap_err(), Error::StaleProposal));
245 })
246 .await
247 }
248
249 #[apply(all_cred_cipher)]
250 #[wasm_bindgen_test]
251 async fn decrypting_duplicate_external_proposal_should_fail(case: TestContext) {
252 let [alice_central, bob_central] = case.sessions().await;
253 Box::pin(async move {
254 let id = conversation_id();
255 alice_central
256 .transaction
257 .new_conversation(&id, case.credential_type, case.cfg.clone())
258 .await
259 .unwrap();
260
261 let epoch = alice_central.transaction.conversation(&id).await.unwrap().epoch().await;
262
263 let ext_proposal = bob_central
264 .transaction
265 .new_external_add_proposal(id.clone(), epoch.into(), case.ciphersuite(), case.credential_type)
266 .await
267 .unwrap();
268
269 alice_central
271 .transaction
272 .conversation(&id)
273 .await
274 .unwrap()
275 .decrypt_message(&ext_proposal.to_bytes().unwrap())
276 .await
277 .unwrap();
278
279 let decryption = alice_central
281 .transaction
282 .conversation(&id)
283 .await
284 .unwrap()
285 .decrypt_message(&ext_proposal.to_bytes().unwrap())
286 .await;
287 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
288
289 alice_central
291 .transaction
292 .conversation(&id)
293 .await
294 .unwrap()
295 .commit_pending_proposals()
296 .await
297 .unwrap();
298
299 let decryption = alice_central
301 .transaction
302 .conversation(&id)
303 .await
304 .unwrap()
305 .decrypt_message(&ext_proposal.to_bytes().unwrap())
306 .await;
307 assert!(matches!(decryption.unwrap_err(), Error::StaleProposal));
308 })
309 .await
310 }
311
312 #[apply(all_cred_cipher)]
314 #[wasm_bindgen_test]
315 async fn decrypting_duplicate_application_message_should_fail(case: TestContext) {
316 let [alice_central, bob_central] = case.sessions().await;
317 Box::pin(async move {
318 let id = conversation_id();
319 alice_central
320 .transaction
321 .new_conversation(&id, case.credential_type, case.cfg.clone())
322 .await
323 .unwrap();
324 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
325
326 let msg = b"Hello bob";
327 let encrypted = alice_central
328 .transaction
329 .conversation(&id)
330 .await
331 .unwrap()
332 .encrypt_message(msg)
333 .await
334 .unwrap();
335
336 bob_central
338 .transaction
339 .conversation(&id)
340 .await
341 .unwrap()
342 .decrypt_message(&encrypted)
343 .await
344 .unwrap();
345 let decryption = bob_central
347 .transaction
348 .conversation(&id)
349 .await
350 .unwrap()
351 .decrypt_message(&encrypted)
352 .await;
353 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
354 })
355 .await
356 }
357}