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