openmls/framing/validation.rs
1//! # Validation steps for incoming messages
2//!
3//! ```text
4//!
5//! MlsMessageIn
6//! │ -.
7//! │ │
8//! │ │
9//! ▼ │
10//! DecryptedMessage +-- parse_message
11//! │ │
12//! │ │
13//! │ │
14//! ▼ -'
15//! UnverifiedMessage
16//! │ -.
17//! │ │
18//! │ +-- process_unverified_message
19//! │ │
20//! ▼ -'
21//! ProcessedMessage
22//!
23//! ```
24
25use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite};
26use proposal_store::QueuedProposal;
27
28use crate::{
29 binary_tree::LeafNodeIndex,
30 ciphersuite::signable::Verifiable,
31 error::LibraryError,
32 extensions::ExternalSendersExtension,
33 group::{errors::ValidationError, mls_group::staged_commit::StagedCommit},
34 tree::sender_ratchet::SenderRatchetConfiguration,
35 versions::ProtocolVersion,
36};
37
38#[cfg(feature = "extensions-draft")]
39use crate::{
40 component::ComponentId,
41 framing::safe_aad::SafeAad,
42 group::{
43 errors::StageCommitError,
44 mls_group::{errors::ResolveAppDataCommitError, processing::UnresolvedAppDataCommit},
45 },
46};
47
48#[cfg(feature = "virtual-clients-draft")]
49use crate::components::vc_commit_data::{VcCommitDataError, VirtualClientCommitData};
50
51use super::{
52 mls_auth_content::AuthenticatedContent,
53 mls_auth_content_in::{AuthenticatedContentIn, VerifiableAuthenticatedContentIn},
54 private_message_in::PrivateMessageIn,
55 public_message_in::PublicMessageIn,
56 *,
57};
58
59/// Result of decrypting an inbound PrivateMessage: either content this client
60/// can process further, or a message this client authored itself, which it
61/// cannot decrypt.
62#[derive(Debug)]
63pub(crate) enum InboundDecryptionResult {
64 /// A message from another sender or from a sibling emulator client (with
65 /// the `virtual-clients-draft` feature), decrypted and ready for parsing.
66 Decrypted(DecryptedMessage),
67 /// A private message whose sender data claims this client's own leaf.
68 /// Carries the plaintext framing fields needed to build the
69 /// [`ProcessedMessage`], since the content itself cannot be decrypted.
70 OwnPrivateMessage {
71 epoch: GroupEpoch,
72 authenticated_data: Vec<u8>,
73 },
74}
75
76impl InboundDecryptionResult {
77 /// Returns the decrypted message, or `None` for an own private message.
78 #[cfg(test)]
79 pub(crate) fn into_decrypted(self) -> Option<DecryptedMessage> {
80 match self {
81 Self::Decrypted(message) => Some(message),
82 Self::OwnPrivateMessage { .. } => None,
83 }
84 }
85}
86
87/// Intermediate message that can be constructed either from a public message or from private message.
88/// If it it constructed from a ciphertext message, the ciphertext message is decrypted first.
89/// This function implements the following checks:
90/// - ValSem005
91/// - ValSem007
92/// - ValSem009
93#[derive(Debug)]
94pub(crate) struct DecryptedMessage {
95 verifiable_content: VerifiableAuthenticatedContentIn,
96 /// Recovered sender emulation-group leaf index for an application
97 /// message from a sibling emulator client.
98 #[cfg(feature = "virtual-clients-draft")]
99 emulator_sender_leaf_index: Option<LeafNodeIndex>,
100}
101
102impl DecryptedMessage {
103 /// Constructs a [DecryptedMessage] from a [VerifiableAuthenticatedContent].
104 pub(crate) fn from_inbound_public_message<'a>(
105 public_message: PublicMessageIn,
106 message_secrets_option: impl Into<Option<&'a MessageSecrets>>,
107 serialized_context: Vec<u8>,
108 crypto: &impl OpenMlsCrypto,
109 ciphersuite: Ciphersuite,
110 ) -> Result<Self, ValidationError> {
111 if public_message.sender().is_member() {
112 // ValSem007 Membership tag presence
113 if public_message.membership_tag().is_none() {
114 return Err(ValidationError::MissingMembershipTag);
115 }
116
117 if let Some(message_secrets) = message_secrets_option.into() {
118 // Verify the membership tag. This needs to be done explicitly for PublicMessage messages,
119 // it is implicit for PrivateMessage messages (because the encryption can only be known by members).
120 // ValSem008
121 // https://validation.openmls.tech/#valn1302
122 public_message.verify_membership(
123 crypto,
124 ciphersuite,
125 message_secrets.membership_key(),
126 message_secrets.serialized_context(),
127 )?;
128 }
129 }
130
131 let verifiable_content = public_message.into_verifiable_content(serialized_context);
132
133 // Public messages don't carry a reuse_guard, so no emulator
134 // sender leaf index to recover.
135 Self::from_verifiable_content(
136 verifiable_content,
137 #[cfg(feature = "virtual-clients-draft")]
138 None,
139 )
140 }
141
142 /// Constructs a [DecryptedMessage] from a [PrivateMessage] by attempting to decrypt it
143 /// to a [VerifiableAuthenticatedContent] first.
144 pub(crate) fn from_inbound_ciphertext(
145 ciphertext: PrivateMessageIn,
146 crypto: &impl OpenMlsCrypto,
147 group: &mut MlsGroup,
148 sender_ratchet_configuration: &SenderRatchetConfiguration,
149 #[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<
150 &crate::framing::private_message::EmulatorReuseGuardCtx<'_>,
151 >,
152 ) -> Result<InboundDecryptionResult, ValidationError> {
153 // This will be refactored with #265.
154 let ciphersuite = group.ciphersuite();
155 // TODO: #819 The old leaves should not be needed any more.
156 // Revisit when the transition is further along.
157 let (message_secrets, _old_leaves) = group
158 .message_secrets_and_leaves(ciphertext.epoch())
159 .map_err(MessageDecryptionError::SecretTreeError)?;
160 let sender_data = ciphertext.sender_data(message_secrets, crypto, ciphersuite)?;
161 let own_sender = sender_data.leaf_index == group.own_leaf_index();
162 // If we are the sender, the content cannot be decrypted and the
163 // signature cannot be verified: the own sender ratchet only produces
164 // encryption keys. Return early before touching any ratchet state so
165 // no decryption counter is consumed and no spurious "generation out
166 // of bounds" error is logged for an own echo.
167 //
168 // With the `virtual-clients-draft` feature, own-leaf messages are only
169 // decryptable when there is an emulator context for this epoch: a
170 // sibling emulator client shares the leaf, and the dual-use ratchet
171 // retains the secrets of unconfirmed own sends. In that case we still
172 // attempt decryption below, and only its failure surfaces the message
173 // as an own private message.
174 //
175 // Without an emulator context the group does not use virtual clients
176 // (which is the case for the emulation group) so an own message is
177 // unambiguously our own echo and we short-circuit just like the non-VC
178 // path.
179 #[cfg(not(feature = "virtual-clients-draft"))]
180 let short_circuit_own = own_sender;
181 #[cfg(feature = "virtual-clients-draft")]
182 let short_circuit_own = own_sender && emulator_ctx.is_none();
183 if short_circuit_own {
184 return Ok(InboundDecryptionResult::OwnPrivateMessage {
185 epoch: ciphertext.epoch(),
186 authenticated_data: ciphertext.aad().to_vec(),
187 });
188 }
189 #[cfg(feature = "virtual-clients-draft")]
190 let effective_emulator_ctx = match emulator_ctx {
191 Some(ctx) if own_sender => Some(ctx),
192 _ => None,
193 };
194 let message_secrets = group
195 .message_secrets_for_epoch_mut(ciphertext.epoch())
196 .map_err(|_| MessageDecryptionError::AeadError)?;
197 let decrypt_result = ciphertext.to_verifiable_content(
198 ciphersuite,
199 crypto,
200 message_secrets,
201 sender_data.leaf_index,
202 sender_ratchet_configuration,
203 sender_data,
204 #[cfg(feature = "virtual-clients-draft")]
205 effective_emulator_ctx,
206 );
207 let decrypted = decrypt_result?;
208 Self::from_verifiable_content(
209 decrypted.verifiable,
210 #[cfg(feature = "virtual-clients-draft")]
211 decrypted.emulator_sender_leaf_index,
212 )
213 .map(InboundDecryptionResult::Decrypted)
214 }
215
216 // Internal constructor function. Does the following checks:
217 // - Confirmation tag must be present for Commit messages
218 // - Membership tag must be present for member messages, if the original incoming message was not an PrivateMessage
219 // - Ensures application messages were originally PrivateMessage messages
220 fn from_verifiable_content(
221 verifiable_content: VerifiableAuthenticatedContentIn,
222 #[cfg(feature = "virtual-clients-draft")] emulator_sender_leaf_index: Option<LeafNodeIndex>,
223 ) -> Result<Self, ValidationError> {
224 // ValSem009
225 if verifiable_content.content_type() == ContentType::Commit
226 && verifiable_content.confirmation_tag().is_none()
227 {
228 return Err(ValidationError::MissingConfirmationTag);
229 }
230 // ValSem005
231 if verifiable_content.content_type() == ContentType::Application {
232 if verifiable_content.wire_format() != WireFormat::PrivateMessage {
233 return Err(ValidationError::UnencryptedApplicationMessage);
234 } else if !verifiable_content.sender().is_member() {
235 // This should not happen because the sender of an PrivateMessage should always be a member
236 return Err(LibraryError::custom("Expected sender to be member.").into());
237 }
238 }
239 Ok(DecryptedMessage {
240 verifiable_content,
241 #[cfg(feature = "virtual-clients-draft")]
242 emulator_sender_leaf_index,
243 })
244 }
245
246 /// Recovered sender emulation-group leaf index, if the message came
247 /// from a sibling emulator client.
248 #[cfg(feature = "virtual-clients-draft")]
249 #[allow(dead_code)]
250 pub(crate) fn emulator_sender_leaf_index(&self) -> Option<LeafNodeIndex> {
251 self.emulator_sender_leaf_index
252 }
253
254 /// Gets the correct credential from the message depending on the sender type.
255 ///
256 /// The closure argument is used to look up the credential and signature key. If the epoch of
257 /// the message is the same as that of the group, look it up in the tree; else, look in up in
258 /// the past trees of the message secret store.
259 ///
260 /// Checks the following semantic validation:
261 /// - ValSem112
262 /// - ValSem245
263 /// - Prepares ValSem246 by setting the right credential. The remainder
264 /// of ValSem246 is validated as part of ValSem010.
265 /// - [valn1301](https://validation.openmls.tech/#valn1301)
266 ///
267 /// Returns the [`Credential`] and the leaf's [`SignaturePublicKey`].
268 pub(crate) fn credential(
269 &self,
270 look_up_credential_with_key: impl Fn(LeafNodeIndex) -> Option<CredentialWithKey>,
271 external_senders: Option<&ExternalSendersExtension>,
272 ) -> Result<CredentialWithKey, ValidationError> {
273 let sender = self.sender();
274 match sender {
275 Sender::Member(leaf_index) => {
276 // https://validation.openmls.tech/#valn1306
277 look_up_credential_with_key(*leaf_index).ok_or(ValidationError::UnknownMember)
278 }
279 Sender::External(index) => {
280 let sender = external_senders
281 .ok_or(ValidationError::NoExternalSendersExtension)?
282 .get(index.index())
283 .ok_or(ValidationError::UnauthorizedExternalSender)?;
284 Ok(CredentialWithKey {
285 credential: sender.credential().clone(),
286 signature_key: sender.signature_key().clone(),
287 })
288 }
289 Sender::NewMemberCommit | Sender::NewMemberProposal => {
290 // Fetch the credential from the message itself.
291 // https://validation.openmls.tech/#valn0407
292 self.verifiable_content.new_member_credential()
293 }
294 }
295 }
296
297 /// Returns the sender.
298 pub fn sender(&self) -> &Sender {
299 self.verifiable_content.sender()
300 }
301
302 /// Returns the [`VerifiableAuthenticatedContent`].
303 pub(crate) fn verifiable_content(&self) -> &VerifiableAuthenticatedContentIn {
304 &self.verifiable_content
305 }
306}
307
308/// Result of [`UnverifiedMessage::verify`].
309pub(crate) struct VerifiedMessage {
310 pub(crate) content: AuthenticatedContent,
311 pub(crate) credential: Credential,
312 #[cfg(feature = "virtual-clients-draft")]
313 pub(crate) emulator_sender_leaf_index: Option<LeafNodeIndex>,
314}
315
316/// Context that is needed to verify the signature of a the leaf node of an
317/// UpdatePath or an update proposal.
318#[derive(Debug, Clone)]
319pub(crate) enum SenderContext {
320 Member((GroupId, LeafNodeIndex)),
321 ExternalCommit {
322 group_id: GroupId,
323 leftmost_blank_index: LeafNodeIndex,
324 self_removes_in_store: Vec<SelfRemoveInStore>,
325 },
326}
327
328/// Partially checked and potentially decrypted message (if it was originally encrypted).
329/// Use this to inspect the [`Credential`] of the message sender
330/// and the optional `aad` if the original message was encrypted.
331/// The [`OpenMlsSignaturePublicKey`] is used to verify the signature of the
332/// message.
333#[derive(Debug, Clone)]
334pub struct UnverifiedMessage {
335 verifiable_content: VerifiableAuthenticatedContentIn,
336 credential: Credential,
337 sender_pk: OpenMlsSignaturePublicKey,
338 sender_context: Option<SenderContext>,
339 /// See [`DecryptedMessage::emulator_sender_leaf_index`].
340 #[cfg(feature = "virtual-clients-draft")]
341 emulator_sender_leaf_index: Option<LeafNodeIndex>,
342}
343
344impl UnverifiedMessage {
345 /// Construct an [UnverifiedMessage] from a [DecryptedMessage] and an optional [Credential].
346 pub(crate) fn from_decrypted_message(
347 decrypted_message: DecryptedMessage,
348 credential: Credential,
349 sender_pk: OpenMlsSignaturePublicKey,
350 sender_context: Option<SenderContext>,
351 ) -> Self {
352 #[cfg(feature = "virtual-clients-draft")]
353 let emulator_sender_leaf_index = decrypted_message.emulator_sender_leaf_index;
354 UnverifiedMessage {
355 verifiable_content: decrypted_message.verifiable_content,
356 credential,
357 sender_pk,
358 sender_context,
359 #[cfg(feature = "virtual-clients-draft")]
360 emulator_sender_leaf_index,
361 }
362 }
363
364 /// Verify the [`UnverifiedMessage`].
365 pub(crate) fn verify(
366 self,
367 ciphersuite: Ciphersuite,
368 crypto: &impl OpenMlsCrypto,
369 protocol_version: ProtocolVersion,
370 ) -> Result<VerifiedMessage, ValidationError> {
371 let content: AuthenticatedContentIn = self
372 .verifiable_content
373 .verify(crypto, &self.sender_pk)
374 .map_err(|_| ValidationError::InvalidSignature)?;
375 // https://validation.openmls.tech/#valn1302
376 // https://validation.openmls.tech/#valn1304
377 let content =
378 content.validate(ciphersuite, crypto, self.sender_context, protocol_version)?;
379 Ok(VerifiedMessage {
380 content,
381 credential: self.credential,
382 #[cfg(feature = "virtual-clients-draft")]
383 emulator_sender_leaf_index: self.emulator_sender_leaf_index,
384 })
385 }
386}
387
388/// A message that has passed all syntax and semantics checks.
389#[derive(Debug)]
390pub struct ProcessedMessage {
391 group_id: GroupId,
392 epoch: GroupEpoch,
393 sender: Sender,
394 authenticated_data: Vec<u8>,
395 content: ProcessedMessageContent,
396 credential: Credential,
397 /// See [`Self::emulator_sender_leaf_index`].
398 #[cfg(feature = "virtual-clients-draft")]
399 emulator_sender_leaf_index: Option<LeafNodeIndex>,
400 /// Parsed Safe AAD prefix, populated only when the message's GroupContext
401 /// required Safe AAD framing. `None` otherwise.
402 #[cfg(feature = "extensions-draft")]
403 safe_aad: Option<SafeAad>,
404 /// Length in bytes of the Safe AAD prefix at the start of
405 /// `authenticated_data`. Zero when [`Self::safe_aad`] is `None`.
406 #[cfg(feature = "extensions-draft")]
407 safe_aad_prefix_len: usize,
408}
409
410impl ProcessedMessage {
411 /// Create a new `ProcessedMessage`.
412 pub(crate) fn new(
413 group_id: GroupId,
414 epoch: GroupEpoch,
415 sender: Sender,
416 authenticated_data: Vec<u8>,
417 content: ProcessedMessageContent,
418 credential: Credential,
419 #[cfg(feature = "virtual-clients-draft")] emulator_sender_leaf_index: Option<LeafNodeIndex>,
420 ) -> Self {
421 Self {
422 group_id,
423 epoch,
424 sender,
425 authenticated_data,
426 content,
427 credential,
428 #[cfg(feature = "virtual-clients-draft")]
429 emulator_sender_leaf_index,
430 #[cfg(feature = "extensions-draft")]
431 safe_aad: None,
432 #[cfg(feature = "extensions-draft")]
433 safe_aad_prefix_len: 0,
434 }
435 }
436
437 /// Swaps an [`ProcessedMessageContent::UnresolvedAppDataCommit`] for the
438 /// [`StagedCommit`] produced by `stage`, keeping all other fields (sender,
439 /// credential, authenticated data, Safe AAD state) intact.
440 ///
441 /// Returns an error if the content is not an unresolved app data commit;
442 /// the message is consumed either way.
443 #[cfg(feature = "extensions-draft")]
444 pub(crate) fn resolve_app_data_commit(
445 mut self,
446 stage: impl FnOnce(UnresolvedAppDataCommit) -> Result<StagedCommit, StageCommitError>,
447 ) -> Result<Self, ResolveAppDataCommitError> {
448 let ProcessedMessageContent::UnresolvedAppDataCommit(unresolved_commit) = self.content
449 else {
450 return Err(ResolveAppDataCommitError::NotAnUnresolvedAppDataCommit);
451 };
452 let staged_commit = stage(*unresolved_commit)?;
453 self.content = ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit));
454 Ok(self)
455 }
456
457 /// Parse the Safe AAD prefix at the start of `authenticated_data` and
458 /// attach it to this message. Callers should invoke this only when the receiving
459 /// group's GroupContext requires Safe AAD framing. Otherwise, `safe_aad`
460 /// stays `None` and `authenticated_data` is the caller-supplied bytes
461 /// untouched.
462 #[cfg(feature = "extensions-draft")]
463 pub(crate) fn try_attach_safe_aad(&mut self) -> Result<(), crate::framing::SafeAadError> {
464 let (safe_aad, prefix_len) =
465 crate::framing::safe_aad::parse_authenticated_data_prefix(&self.authenticated_data)?;
466 self.safe_aad = Some(safe_aad);
467 self.safe_aad_prefix_len = prefix_len;
468 Ok(())
469 }
470
471 /// Returns the parsed Safe AAD struct, or `None` if Safe AAD was not
472 /// active for the group this message belongs to.
473 #[cfg(feature = "extensions-draft")]
474 pub fn safe_aad(&self) -> Option<&SafeAad> {
475 self.safe_aad.as_ref()
476 }
477
478 /// Look up a Safe AAD item by [`ComponentId`].
479 #[cfg(feature = "extensions-draft")]
480 pub fn safe_aad_item(&self, component_id: crate::component::ComponentId) -> Option<&[u8]> {
481 self.safe_aad
482 .as_ref()
483 .and_then(|safe_aad| safe_aad.get(component_id))
484 }
485
486 /// Parse the virtual-clients commit data from this message's Safe AAD.
487 ///
488 /// Returns `Ok(None)` when the message carries no Safe AAD item under
489 /// [`VC_COMPONENT_ID`], which includes the case where Safe AAD was not
490 /// active for the group.
491 ///
492 /// [`VC_COMPONENT_ID`]: crate::components::vc_derivation_info::VC_COMPONENT_ID
493 #[cfg(feature = "virtual-clients-draft")]
494 pub fn vc_commit_data(&self) -> Result<Option<VirtualClientCommitData>, VcCommitDataError> {
495 let Some(safe_aad) = self.safe_aad.as_ref() else {
496 return Ok(None);
497 };
498 VirtualClientCommitData::from_safe_aad(safe_aad)
499 }
500
501 /// Returns the bytes of `authenticated_data` after any Safe AAD prefix.
502 /// Equal to [`Self::aad`] when no Safe AAD prefix is present.
503 #[cfg(feature = "extensions-draft")]
504 pub fn tail_aad(&self) -> &[u8] {
505 &self.authenticated_data[self.safe_aad_prefix_len..]
506 }
507
508 /// Returns the sender's leaf index in the emulation group when this
509 /// message is an application message from a sibling emulator client.
510 #[cfg(feature = "virtual-clients-draft")]
511 pub fn emulator_sender_leaf_index(&self) -> Option<LeafNodeIndex> {
512 self.emulator_sender_leaf_index
513 }
514
515 /// Returns the group ID of the message.
516 pub fn group_id(&self) -> &GroupId {
517 &self.group_id
518 }
519
520 /// Returns the epoch of the message.
521 pub fn epoch(&self) -> GroupEpoch {
522 self.epoch
523 }
524
525 /// Returns the sender of the message.
526 pub fn sender(&self) -> &Sender {
527 &self.sender
528 }
529
530 /// Returns the additional authenticated data (AAD) of the message.
531 pub fn aad(&self) -> &[u8] {
532 &self.authenticated_data
533 }
534
535 /// Returns the content of the message.
536 pub fn content(&self) -> &ProcessedMessageContent {
537 &self.content
538 }
539
540 /// Returns the content of the message and consumes the message.
541 pub fn into_content(self) -> ProcessedMessageContent {
542 self.content
543 }
544
545 /// Returns the credential of the message.
546 pub fn credential(&self) -> &Credential {
547 &self.credential
548 }
549
550 /// Safely export a value if the content of the processed message is a
551 /// [`StagedCommit`].
552 #[cfg(feature = "extensions-draft")]
553 pub fn safe_export_secret<Crypto: OpenMlsCrypto>(
554 &mut self,
555 crypto: &Crypto,
556 component_id: ComponentId,
557 ) -> Result<Vec<u8>, ProcessedMessageSafeExportSecretError> {
558 if let ProcessedMessageContent::StagedCommitMessage(ref mut staged_commit) =
559 &mut self.content
560 {
561 let secret = staged_commit.safe_export_secret(crypto, component_id)?;
562 Ok(secret)
563 } else {
564 Err(ProcessedMessageSafeExportSecretError::NotACommit)
565 }
566 }
567}
568
569/// Content of a processed message.
570///
571/// See the content variants' documentation for more information.
572/// [`StagedCommit`] and [`QueuedProposal`] can be inspected for authorization purposes.
573#[derive(Debug)]
574pub enum ProcessedMessageContent {
575 /// An application message.
576 ///
577 /// The [`ApplicationMessage`] contains a vector of bytes that can be used right-away.
578 ApplicationMessage(ApplicationMessage),
579 /// A standalone proposal.
580 ///
581 /// The [`QueuedProposal`] can be inspected for authorization purposes by the application.
582 /// If the proposal is deemed to be allowed, it should be added to the group's proposal
583 /// queue using [`MlsGroup::store_pending_proposal()`](crate::group::mls_group::MlsGroup::store_pending_proposal()).
584 ProposalMessage(Box<QueuedProposal>),
585 /// An [external join proposal](crate::prelude::JoinProposal) sent by a
586 /// [NewMemberProposal](crate::prelude::Sender::NewMemberProposal) sender which is outside the group.
587 ///
588 /// Since this originates from a party outside the group, the [`QueuedProposal`] SHOULD be
589 /// inspected for authorization purposes by the application. If the proposal is deemed to be
590 /// allowed, it should be added to the group's proposal queue using
591 /// [`MlsGroup::store_pending_proposal()`](crate::group::mls_group::MlsGroup::store_pending_proposal()).
592 ExternalJoinProposalMessage(Box<QueuedProposal>),
593 /// A Commit message.
594 ///
595 /// The [`StagedCommit`] can be inspected for authorization purposes by the application.
596 /// If the type of the commit and the proposals it covers are deemed to be allowed,
597 /// the commit should be merged into the group's state using
598 /// [`MlsGroup::merge_staged_commit()`](crate::group::mls_group::MlsGroup::merge_staged_commit()).
599 StagedCommitMessage(Box<StagedCommit>),
600 /// A Commit authored by this client that it got fanned out by the delivery
601 /// service, matching the group's pending commit.
602 ///
603 /// This is returned instead of
604 /// [`StagedCommitMessage`](Self::StagedCommitMessage) when the processed
605 /// Commit was created by this client and matches the group's pending commit.
606 /// Since this client already holds the corresponding pending commit, the
607 /// incoming Commit is not staged. To apply it, merge the pending commit
608 /// using
609 /// [`MlsGroup::merge_pending_commit()`](crate::group::mls_group::MlsGroup::merge_pending_commit()).
610 /// An own Commit that does not match the pending commit is instead returned
611 /// as a [`StagedCommitMessage`](Self::StagedCommitMessage) (if it has no
612 /// UpdatePath) or rejected (if it has an UpdatePath we cannot decrypt).
613 ///
614 /// The match against the pending commit is established by comparing the
615 /// confirmation tag of the incoming Commit against the one stored with the
616 /// pending commit. The message signature has already been verified, which
617 /// authenticates the Commit as ours, and a matching confirmation tag binds
618 /// the confirmed transcript hash of the new epoch. We do not otherwise
619 /// compare the contents of the incoming Commit against the pending commit,
620 /// and the incoming Commit's state is never adopted.
621 ///
622 /// This is only produced for Commits framed as
623 /// [`PublicMessage`](crate::framing::MlsMessageBodyIn::PublicMessage). A
624 /// Commit framed as a
625 /// [`PrivateMessage`](crate::framing::MlsMessageBodyIn::PrivateMessage)
626 /// cannot be decrypted by its own author and instead surfaces as
627 /// [`OwnPrivateMessage`](Self::OwnPrivateMessage). The exception is the
628 /// `virtual-clients-draft` feature, where an own private Commit whose
629 /// encryption secret is still retained (not yet confirmed) decrypts and
630 /// can produce this variant as well. Under that feature the pending-commit
631 /// match is checked before any sibling-commit (virtual clients) material is
632 /// loaded, so an own Commit fanned back by the delivery service surfaces as
633 /// `OwnPendingCommit` without consuming an operation-secret generation from
634 /// the derivation epoch's operation secret tree.
635 OwnPendingCommit,
636 /// A PrivateMessage whose sender data claims this client's own leaf index,
637 /// i.e. a message this client authored that the delivery service fanned
638 /// back.
639 ///
640 /// The content cannot be decrypted (the own sender ratchet is
641 /// encryption-only) and the signature cannot be verified.
642 ///
643 /// Applications should treat this variant as a hint to skip the message.
644 /// The content type of the incoming message (application/proposal/commit)
645 /// is available via `ProtocolMessage::content_type()` before processing,
646 /// and is unauthenticated plaintext in the PrivateMessage framing.
647 ///
648 /// With the `virtual-clients-draft` feature, own-leaf messages are
649 /// decryptable while their secrets are retained: unconfirmed own sends
650 /// and messages from sibling emulator clients decrypt and process
651 /// normally. This variant is then only returned in groups that do not
652 /// use virtual clients (no derivation epoch state registered for the
653 /// message's epoch), when decryption of an own message fails, e.g. because
654 /// the send was already confirmed via
655 /// `MlsGroup::confirm_application_message()`.
656 OwnPrivateMessage,
657 /// A Commit message covering AppDataUpdate proposals.
658 ///
659 /// The proposals carry diffs in an application-defined format, so the
660 /// commit cannot be staged before the application has interpreted them and
661 /// computed the resulting dictionary entries. Inspect the proposals via
662 /// [`UnresolvedAppDataCommit::app_data_update_proposals()`], compute the
663 /// updates with the help of
664 /// [`MlsGroup::app_data_dictionary_updater()`](crate::group::mls_group::MlsGroup::app_data_dictionary_updater)
665 /// and resume staging via
666 /// [`MlsGroup::stage_app_data_commit()`](crate::group::mls_group::MlsGroup::stage_app_data_commit).
667 ///
668 /// This variant is likewise returned by
669 /// [`PublicGroup::process_message()`](crate::group::public_group::PublicGroup::process_message),
670 /// where the updates are computed with
671 /// [`PublicGroup::app_data_dictionary_updater()`](crate::group::public_group::PublicGroup::app_data_dictionary_updater)
672 /// and staging resumes via
673 /// [`PublicGroup::stage_app_data_commit()`](crate::group::public_group::PublicGroup::stage_app_data_commit).
674 #[cfg(feature = "extensions-draft")]
675 UnresolvedAppDataCommit(Box<UnresolvedAppDataCommit>),
676}
677
678/// Application message received through a [ProcessedMessage].
679#[derive(Debug, PartialEq, Eq)]
680pub struct ApplicationMessage {
681 bytes: Vec<u8>,
682}
683
684impl ApplicationMessage {
685 /// Create a new [ApplicationMessage].
686 pub(crate) fn new(bytes: Vec<u8>) -> Self {
687 Self { bytes }
688 }
689
690 /// Returns the inner bytes and consumes the [`ApplicationMessage`].
691 pub fn into_bytes(self) -> Vec<u8> {
692 self.bytes
693 }
694}