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}