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::test_utils::*;
50 use wasm_bindgen_test::*;
51
52 wasm_bindgen_test_configure!(run_in_browser);
53
54 #[apply(all_cred_cipher)]
55 #[wasm_bindgen_test]
56 async fn decrypting_duplicate_member_commit_should_fail(case: TestContext) {
57 if case.is_pure_ciphertext() {
59 return;
60 }
61
62 let [alice, bob] = case.sessions().await;
63 Box::pin(async move {
64 let conversation = case.create_conversation([&alice, &bob]).await;
65
66 let commit_guard = conversation.update_unmerged().await;
68 let unknown_commit = commit_guard.message();
69 let conversation = commit_guard.finish();
70 conversation.guard().await.clear_pending_commit().await.unwrap();
71
72 let commit_guard = conversation.update().await;
73 let commit = commit_guard.message();
74
75 let conversation = commit_guard.notify_members().await;
77 let decrypt_duplicate = conversation
79 .guard_of(&bob)
80 .await
81 .decrypt_message(&commit.to_bytes().unwrap())
82 .await;
83 assert!(matches!(decrypt_duplicate.unwrap_err(), Error::DuplicateMessage));
84
85 let decrypt_lost_commit = conversation
88 .guard_of(&bob)
89 .await
90 .decrypt_message(&unknown_commit.to_bytes().unwrap())
91 .await;
92 assert!(matches!(decrypt_lost_commit.unwrap_err(), Error::StaleCommit));
93 })
94 .await
95 }
96
97 #[apply(all_cred_cipher)]
98 #[wasm_bindgen_test]
99 async fn decrypting_duplicate_external_commit_should_fail(case: TestContext) {
100 let [alice, bob] = case.sessions().await;
101 Box::pin(async move {
102 let conversation = case.create_conversation([&alice]).await;
103
104 let (commit_guard, mut pending_conversation) = conversation.external_join_unmerged(&bob).await;
106 let unknown_ext_commit = commit_guard.message();
107 pending_conversation.clear().await.unwrap();
108 let conversation = commit_guard.finish();
109
110 let commit_guard = conversation.external_join(&bob).await;
111 let ext_commit = commit_guard.message();
112
113 let conversation = commit_guard.notify_members().await;
115 let decryption = conversation
117 .guard()
118 .await
119 .decrypt_message(&ext_commit.to_bytes().unwrap())
120 .await;
121 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
122
123 let decryption = conversation
126 .guard()
127 .await
128 .decrypt_message(&unknown_ext_commit.to_bytes().unwrap())
129 .await;
130 assert!(matches!(decryption.unwrap_err(), Error::StaleCommit));
131 })
132 .await
133 }
134
135 #[apply(all_cred_cipher)]
136 #[wasm_bindgen_test]
137 async fn decrypting_duplicate_proposal_should_fail(case: TestContext) {
138 let [alice, bob] = case.sessions().await;
139 Box::pin(async move {
140 let conversation = case.create_conversation([&alice, &bob]).await;
141
142 let proposal_guard = conversation.update_proposal().await;
143 let proposal = proposal_guard.message();
144
145 let conversation = proposal_guard.notify_members().await;
147
148 let decryption = conversation
150 .guard_of(&bob)
151 .await
152 .decrypt_message(&proposal.to_bytes().unwrap())
153 .await;
154 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
155
156 let conversation = conversation
158 .acting_as(&bob)
159 .await
160 .commit_pending_proposals_notify()
161 .await;
162
163 let decryption = conversation
165 .guard_of(&bob)
166 .await
167 .decrypt_message(&proposal.to_bytes().unwrap())
168 .await;
169 assert!(matches!(decryption.unwrap_err(), Error::StaleProposal));
170 })
171 .await
172 }
173
174 #[apply(all_cred_cipher)]
175 #[wasm_bindgen_test]
176 async fn decrypting_duplicate_external_proposal_should_fail(case: TestContext) {
177 let [alice, bob] = case.sessions().await;
178 Box::pin(async move {
179 let conversation = case.create_conversation([&alice]).await;
180
181 let proposal_guard = conversation.external_join_proposal(&bob).await;
182 let proposal = proposal_guard.message();
183
184 let conversation = proposal_guard.notify_members().await;
186
187 let decryption = conversation
189 .guard()
190 .await
191 .decrypt_message(&proposal.to_bytes().unwrap())
192 .await;
193 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
194
195 let conversation = conversation.commit_pending_proposals_notify().await;
197
198 let decryption = conversation
200 .guard()
201 .await
202 .decrypt_message(&proposal.to_bytes().unwrap())
203 .await;
204 assert!(matches!(decryption.unwrap_err(), Error::StaleProposal));
205 })
206 .await
207 }
208
209 #[apply(all_cred_cipher)]
211 #[wasm_bindgen_test]
212 async fn decrypting_duplicate_application_message_should_fail(case: TestContext) {
213 let [alice, bob] = case.sessions().await;
214 Box::pin(async move {
215 let conversation = case.create_conversation([&alice, &bob]).await;
216
217 let msg = b"Hello bob";
218 let encrypted = conversation.guard().await.encrypt_message(msg).await.unwrap();
219
220 conversation
222 .guard_of(&bob)
223 .await
224 .decrypt_message(&encrypted)
225 .await
226 .unwrap();
227 let decryption = conversation.guard_of(&bob).await.decrypt_message(&encrypted).await;
229 assert!(matches!(decryption.unwrap_err(), Error::DuplicateMessage));
230 })
231 .await
232 }
233}