openmls/group/public_group/process.rs
1//! This module contains the implementation of the processing functions for
2//! public groups.
3
4use openmls_traits::crypto::OpenMlsCrypto;
5use tls_codec::Serialize;
6
7use crate::{
8 ciphersuite::OpenMlsSignaturePublicKey,
9 credentials::CredentialWithKey,
10 error::LibraryError,
11 framing::{
12 mls_content::FramedContentBody, ApplicationMessage, DecryptedMessage, ProcessedMessage,
13 ProcessedMessageContent, ProtocolMessage, Sender, SenderContext, UnverifiedMessage,
14 },
15 group::{
16 errors::ValidationError, mls_group::errors::ProcessMessageError,
17 past_secrets::MessageSecretsStore, proposal_store::QueuedProposal,
18 },
19 messages::proposals::Proposal,
20};
21
22use super::PublicGroup;
23
24impl PublicGroup {
25 /// This function is used to parse messages from the DS.
26 /// It checks for syntactic errors and makes some semantic checks as well.
27 /// If the input is a [PrivateMessage] message, it will be decrypted.
28 /// Returns an [UnverifiedMessage] that can be inspected and later processed in
29 /// [Self::process_unverified_message()].
30 /// Checks the following semantic validation:
31 /// - ValSem002
32 /// - ValSem003
33 /// - ValSem004
34 /// - ValSem005
35 /// - ValSem006
36 /// - ValSem007
37 /// - ValSem009
38 /// - ValSem112
39 /// - ValSem245
40 pub(crate) fn parse_message<'a>(
41 &self,
42 decrypted_message: DecryptedMessage,
43 message_secrets_store_option: impl Into<Option<&'a MessageSecretsStore>>,
44 ) -> Result<UnverifiedMessage, ValidationError> {
45 let message_secrets_store_option = message_secrets_store_option.into();
46 let verifiable_content = decrypted_message.verifiable_content();
47
48 // Checks the following semantic validation:
49 // - ValSem004
50 // - ValSem005
51 // - ValSem009
52 self.validate_verifiable_content(verifiable_content, message_secrets_store_option)?;
53
54 let message_epoch = verifiable_content.epoch();
55
56 // Depending on the epoch of the message, use the correct set of leaf nodes for getting the
57 // credential and signature key for the member with given index.
58 let look_up_credential_with_key = |leaf_node_index| {
59 if message_epoch == self.group_context().epoch() {
60 self.treesync()
61 .leaf(leaf_node_index)
62 .map(CredentialWithKey::from)
63 } else if let Some(store) = message_secrets_store_option {
64 store
65 .leaves_for_epoch(message_epoch)
66 .get(leaf_node_index.u32() as usize)
67 .map(CredentialWithKey::from)
68 } else {
69 None
70 }
71 };
72
73 // Extract the credential if the sender is a member or a new member.
74 // Checks the following semantic validation:
75 // - ValSem112
76 // - ValSem245
77 // - Prepares ValSem246 by setting the right credential. The remainder
78 // of ValSem246 is validated as part of ValSem010.
79 // External senders are not supported yet #106/#151.
80 let CredentialWithKey {
81 credential,
82 signature_key,
83 } = decrypted_message.credential(
84 look_up_credential_with_key,
85 self.group_context().extensions().external_senders(),
86 )?;
87 let signature_public_key = OpenMlsSignaturePublicKey::from_signature_key(
88 signature_key,
89 self.ciphersuite().signature_algorithm(),
90 );
91
92 // For commit messages, we need to check if the sender is a member or a
93 // new member and set the tree position accordingly.
94 let sender_context = match decrypted_message.sender() {
95 Sender::Member(leaf_index) => Some(SenderContext::Member((
96 self.group_id().clone(),
97 *leaf_index,
98 ))),
99 Sender::NewMemberCommit => Some(SenderContext::ExternalCommit((
100 self.group_id().clone(),
101 self.treesync().free_leaf_index(),
102 ))),
103 Sender::External(_) | Sender::NewMemberProposal => None,
104 };
105
106 Ok(UnverifiedMessage::from_decrypted_message(
107 decrypted_message,
108 credential,
109 signature_public_key,
110 sender_context,
111 ))
112 }
113
114 /// This function is used to parse messages from the DS. It checks for
115 /// syntactic errors and does semantic validation as well. It returns a
116 /// [ProcessedMessage] enum. Checks the following semantic validation:
117 /// - ValSem002
118 /// - ValSem003
119 /// - ValSem004
120 /// - ValSem005
121 /// - ValSem006
122 /// - ValSem007
123 /// - ValSem008
124 /// - ValSem009
125 /// - ValSem010
126 /// - ValSem101
127 /// - ValSem102
128 /// - ValSem104
129 /// - ValSem106
130 /// - ValSem107
131 /// - ValSem108
132 /// - ValSem110
133 /// - ValSem111
134 /// - ValSem112
135 /// - ValSem200
136 /// - ValSem201
137 /// - ValSem202: Path must be the right length
138 /// - ValSem203: Path secrets must decrypt correctly
139 /// - ValSem204: Public keys from Path must be verified and match the
140 /// private keys from the direct path
141 /// - ValSem205
142 /// - ValSem240
143 /// - ValSem241
144 /// - ValSem242
145 /// - ValSem244
146 /// - ValSem245
147 /// - ValSem246 (as part of ValSem010)
148 pub fn process_message(
149 &self,
150 crypto: &impl OpenMlsCrypto,
151 message: impl Into<ProtocolMessage>,
152 ) -> Result<ProcessedMessage, ProcessMessageError> {
153 let protocol_message = message.into();
154 // Checks the following semantic validation:
155 // - ValSem002
156 // - ValSem003
157 self.validate_framing(&protocol_message)?;
158
159 let decrypted_message = match protocol_message {
160 ProtocolMessage::PrivateMessage(_) => {
161 return Err(ProcessMessageError::IncompatibleWireFormat)
162 }
163 ProtocolMessage::PublicMessage(public_message) => {
164 DecryptedMessage::from_inbound_public_message(
165 *public_message,
166 None,
167 self.group_context()
168 .tls_serialize_detached()
169 .map_err(LibraryError::missing_bound_check)?,
170 crypto,
171 self.ciphersuite(),
172 )?
173 }
174 };
175
176 let unverified_message = self
177 .parse_message(decrypted_message, None)
178 .map_err(ProcessMessageError::from)?;
179 self.process_unverified_message(crypto, unverified_message)
180 }
181}
182
183impl PublicGroup {
184 /// This processing function does most of the semantic verifications.
185 /// It returns a [ProcessedMessage] enum.
186 /// Checks the following semantic validation:
187 /// - ValSem008
188 /// - ValSem010
189 /// - ValSem101
190 /// - ValSem102
191 /// - ValSem104
192 /// - ValSem106
193 /// - ValSem107
194 /// - ValSem108
195 /// - ValSem110
196 /// - ValSem111
197 /// - ValSem112
198 /// - ValSem200
199 /// - ValSem201
200 /// - ValSem202: Path must be the right length
201 /// - ValSem203: Path secrets must decrypt correctly
202 /// - ValSem204: Public keys from Path must be verified and match the
203 /// private keys from the direct path
204 /// - ValSem205
205 /// - ValSem240
206 /// - ValSem241
207 /// - ValSem242
208 /// - ValSem244
209 /// - ValSem246 (as part of ValSem010)
210 pub(crate) fn process_unverified_message(
211 &self,
212 crypto: &impl OpenMlsCrypto,
213 unverified_message: UnverifiedMessage,
214 ) -> Result<ProcessedMessage, ProcessMessageError> {
215 // Checks the following semantic validation:
216 // - ValSem010
217 // - ValSem246 (as part of ValSem010)
218 // - https://validation.openmls.tech/#valn1203
219 let (content, credential) =
220 unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
221
222 match content.sender() {
223 Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => {
224 let sender = content.sender().clone();
225 let authenticated_data = content.authenticated_data().to_owned();
226
227 let content = match content.content() {
228 FramedContentBody::Application(application_message) => {
229 ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
230 application_message.as_slice().to_owned(),
231 ))
232 }
233 FramedContentBody::Proposal(_) => {
234 let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
235 self.ciphersuite(),
236 crypto,
237 content,
238 )?);
239 if matches!(sender, Sender::NewMemberProposal) {
240 ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
241 } else {
242 ProcessedMessageContent::ProposalMessage(proposal)
243 }
244 }
245 FramedContentBody::Commit(_) => {
246 let staged_commit = self.stage_commit(&content, crypto)?;
247 ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
248 }
249 };
250
251 Ok(ProcessedMessage::new(
252 self.group_id().clone(),
253 self.group_context().epoch(),
254 sender,
255 authenticated_data,
256 content,
257 credential,
258 ))
259 }
260 Sender::External(_) => {
261 let sender = content.sender().clone();
262 let data = content.authenticated_data().to_owned();
263 // https://validation.openmls.tech/#valn1501
264 match content.content() {
265 FramedContentBody::Application(_) => {
266 Err(ProcessMessageError::UnauthorizedExternalApplicationMessage)
267 }
268 // TODO: https://validation.openmls.tech/#valn1502
269 FramedContentBody::Proposal(Proposal::GroupContextExtensions(_)) => {
270 let content = ProcessedMessageContent::ProposalMessage(Box::new(
271 QueuedProposal::from_authenticated_content_by_ref(
272 self.ciphersuite(),
273 crypto,
274 content,
275 )?,
276 ));
277 Ok(ProcessedMessage::new(
278 self.group_id().clone(),
279 self.group_context().epoch(),
280 sender,
281 data,
282 content,
283 credential,
284 ))
285 }
286
287 FramedContentBody::Proposal(Proposal::Remove(_)) => {
288 let content = ProcessedMessageContent::ProposalMessage(Box::new(
289 QueuedProposal::from_authenticated_content_by_ref(
290 self.ciphersuite(),
291 crypto,
292 content,
293 )?,
294 ));
295 Ok(ProcessedMessage::new(
296 self.group_id().clone(),
297 self.group_context().epoch(),
298 sender,
299 data,
300 content,
301 credential,
302 ))
303 }
304 FramedContentBody::Proposal(Proposal::Add(_)) => {
305 let content = ProcessedMessageContent::ProposalMessage(Box::new(
306 QueuedProposal::from_authenticated_content_by_ref(
307 self.ciphersuite(),
308 crypto,
309 content,
310 )?,
311 ));
312 Ok(ProcessedMessage::new(
313 self.group_id().clone(),
314 self.group_context().epoch(),
315 sender,
316 data,
317 content,
318 credential,
319 ))
320 }
321 // TODO #151/#106
322 FramedContentBody::Proposal(_) => {
323 Err(ProcessMessageError::UnsupportedProposalType)
324 }
325 FramedContentBody::Commit(_) => {
326 Err(ProcessMessageError::UnauthorizedExternalCommitMessage)
327 }
328 }
329 }
330 }
331 }
332}