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, ProposalOrRefType},
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 match content.content() {
264 FramedContentBody::Application(_) => {
265 Err(ProcessMessageError::UnauthorizedExternalApplicationMessage)
266 }
267 FramedContentBody::Proposal(Proposal::Remove(_)) => {
268 let content = ProcessedMessageContent::ProposalMessage(Box::new(
269 QueuedProposal::from_authenticated_content_by_ref(
270 self.ciphersuite(),
271 crypto,
272 content,
273 )?,
274 ));
275 Ok(ProcessedMessage::new(
276 self.group_id().clone(),
277 self.group_context().epoch(),
278 sender,
279 data,
280 content,
281 credential,
282 ))
283 }
284 FramedContentBody::Proposal(Proposal::Add(_)) => {
285 let content = ProcessedMessageContent::ProposalMessage(Box::new(
286 QueuedProposal::from_authenticated_content(
287 self.ciphersuite(),
288 crypto,
289 content,
290 ProposalOrRefType::Proposal,
291 )?,
292 ));
293 Ok(ProcessedMessage::new(
294 self.group_id().clone(),
295 self.group_context().epoch(),
296 sender,
297 data,
298 content,
299 credential,
300 ))
301 }
302 // TODO #151/#106
303 FramedContentBody::Proposal(_) => {
304 Err(ProcessMessageError::UnsupportedProposalType)
305 }
306 FramedContentBody::Commit(_) => {
307 Err(ProcessMessageError::UnauthorizedExternalCommitMessage)
308 }
309 }
310 }
311 }
312 }
313}