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}