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
38use super::{
39    mls_auth_content::AuthenticatedContent,
40    mls_auth_content_in::{AuthenticatedContentIn, VerifiableAuthenticatedContentIn},
41    private_message_in::PrivateMessageIn,
42    public_message_in::PublicMessageIn,
43    *,
44};
45
46/// Intermediate message that can be constructed either from a public message or from private message.
47/// If it it constructed from a ciphertext message, the ciphertext message is decrypted first.
48/// This function implements the following checks:
49///  - ValSem005
50///  - ValSem007
51///  - ValSem009
52#[derive(Debug)]
53pub(crate) struct DecryptedMessage {
54    verifiable_content: VerifiableAuthenticatedContentIn,
55}
56
57impl DecryptedMessage {
58    /// Constructs a [DecryptedMessage] from a [VerifiableAuthenticatedContent].
59    pub(crate) fn from_inbound_public_message<'a>(
60        public_message: PublicMessageIn,
61        message_secrets_option: impl Into<Option<&'a MessageSecrets>>,
62        serialized_context: Vec<u8>,
63        crypto: &impl OpenMlsCrypto,
64        ciphersuite: Ciphersuite,
65    ) -> Result<Self, ValidationError> {
66        if public_message.sender().is_member() {
67            // ValSem007 Membership tag presence
68            if public_message.membership_tag().is_none() {
69                return Err(ValidationError::MissingMembershipTag);
70            }
71
72            if let Some(message_secrets) = message_secrets_option.into() {
73                // Verify the membership tag. This needs to be done explicitly for PublicMessage messages,
74                // it is implicit for PrivateMessage messages (because the encryption can only be known by members).
75                // ValSem008
76                // https://validation.openmls.tech/#valn1302
77                public_message.verify_membership(
78                    crypto,
79                    ciphersuite,
80                    message_secrets.membership_key(),
81                    message_secrets.serialized_context(),
82                )?;
83            }
84        }
85
86        let verifiable_content = public_message.into_verifiable_content(serialized_context);
87
88        Self::from_verifiable_content(verifiable_content)
89    }
90
91    /// Constructs a [DecryptedMessage] from a [PrivateMessage] by attempting to decrypt it
92    /// to a [VerifiableAuthenticatedContent] first.
93    pub(crate) fn from_inbound_ciphertext(
94        ciphertext: PrivateMessageIn,
95        crypto: &impl OpenMlsCrypto,
96        group: &mut MlsGroup,
97        sender_ratchet_configuration: &SenderRatchetConfiguration,
98    ) -> Result<Self, ValidationError> {
99        // This will be refactored with #265.
100        let ciphersuite = group.ciphersuite();
101        // TODO: #819 The old leaves should not be needed any more.
102        //       Revisit when the transition is further along.
103        let (message_secrets, _old_leaves) = group
104            .message_secrets_and_leaves_mut(ciphertext.epoch())
105            .map_err(|_| MessageDecryptionError::AeadError)?;
106        let sender_data = ciphertext.sender_data(message_secrets, crypto, ciphersuite)?;
107        // Check if we are the sender
108        if sender_data.leaf_index == group.own_leaf_index() {
109            return Err(ValidationError::CannotDecryptOwnMessage);
110        }
111        let message_secrets = group
112            .message_secrets_mut(ciphertext.epoch())
113            .map_err(|_| MessageDecryptionError::AeadError)?;
114        let verifiable_content = ciphertext.to_verifiable_content(
115            ciphersuite,
116            crypto,
117            message_secrets,
118            sender_data.leaf_index,
119            sender_ratchet_configuration,
120            sender_data,
121        )?;
122        Self::from_verifiable_content(verifiable_content)
123    }
124
125    // Internal constructor function. Does the following checks:
126    // - Confirmation tag must be present for Commit messages
127    // - Membership tag must be present for member messages, if the original incoming message was not an PrivateMessage
128    // - Ensures application messages were originally PrivateMessage messages
129    fn from_verifiable_content(
130        verifiable_content: VerifiableAuthenticatedContentIn,
131    ) -> Result<Self, ValidationError> {
132        // ValSem009
133        if verifiable_content.content_type() == ContentType::Commit
134            && verifiable_content.confirmation_tag().is_none()
135        {
136            return Err(ValidationError::MissingConfirmationTag);
137        }
138        // ValSem005
139        if verifiable_content.content_type() == ContentType::Application {
140            if verifiable_content.wire_format() != WireFormat::PrivateMessage {
141                return Err(ValidationError::UnencryptedApplicationMessage);
142            } else if !verifiable_content.sender().is_member() {
143                // This should not happen because the sender of an PrivateMessage should always be a member
144                return Err(LibraryError::custom("Expected sender to be member.").into());
145            }
146        }
147        Ok(DecryptedMessage { verifiable_content })
148    }
149
150    /// Gets the correct credential from the message depending on the sender type.
151    ///
152    /// The closure argument is used to look up the credential and signature key. If the epoch of
153    /// the message is the same as that of the group, look it up in the tree; else, look in up in
154    /// the past trees of the message secret store.
155    ///
156    /// Checks the following semantic validation:
157    ///  - ValSem112
158    ///  - ValSem245
159    ///  - Prepares ValSem246 by setting the right credential. The remainder
160    ///    of ValSem246 is validated as part of ValSem010.
161    ///  - [valn1301](https://validation.openmls.tech/#valn1301)
162    ///
163    /// Returns the [`Credential`] and the leaf's [`SignaturePublicKey`].
164    pub(crate) fn credential(
165        &self,
166        look_up_credential_with_key: impl Fn(LeafNodeIndex) -> Option<CredentialWithKey>,
167        external_senders: Option<&ExternalSendersExtension>,
168    ) -> Result<CredentialWithKey, ValidationError> {
169        let sender = self.sender();
170        match sender {
171            Sender::Member(leaf_index) => {
172                // https://validation.openmls.tech/#valn1306
173                look_up_credential_with_key(*leaf_index).ok_or(ValidationError::UnknownMember)
174            }
175            Sender::External(index) => {
176                let sender = external_senders
177                    .ok_or(ValidationError::NoExternalSendersExtension)?
178                    .get(index.index())
179                    .ok_or(ValidationError::UnauthorizedExternalSender)?;
180                Ok(CredentialWithKey {
181                    credential: sender.credential().clone(),
182                    signature_key: sender.signature_key().clone(),
183                })
184            }
185            Sender::NewMemberCommit | Sender::NewMemberProposal => {
186                // Fetch the credential from the message itself.
187                // https://validation.openmls.tech/#valn0407
188                self.verifiable_content.new_member_credential()
189            }
190        }
191    }
192
193    /// Returns the sender.
194    pub fn sender(&self) -> &Sender {
195        self.verifiable_content.sender()
196    }
197
198    /// Returns the [`VerifiableAuthenticatedContent`].
199    pub(crate) fn verifiable_content(&self) -> &VerifiableAuthenticatedContentIn {
200        &self.verifiable_content
201    }
202}
203
204/// Context that is needed to verify the signature of a the leaf node of an
205/// UpdatePath or an update proposal.
206#[derive(Debug, Clone)]
207pub(crate) enum SenderContext {
208    Member((GroupId, LeafNodeIndex)),
209    ExternalCommit((GroupId, LeafNodeIndex)),
210}
211
212/// Partially checked and potentially decrypted message (if it was originally encrypted).
213/// Use this to inspect the [`Credential`] of the message sender
214/// and the optional `aad` if the original message was encrypted.
215/// The [`OpenMlsSignaturePublicKey`] is used to verify the signature of the
216/// message.
217#[derive(Debug, Clone)]
218pub(crate) struct UnverifiedMessage {
219    verifiable_content: VerifiableAuthenticatedContentIn,
220    credential: Credential,
221    sender_pk: OpenMlsSignaturePublicKey,
222    sender_context: Option<SenderContext>,
223}
224
225impl UnverifiedMessage {
226    /// Construct an [UnverifiedMessage] from a [DecryptedMessage] and an optional [Credential].
227    pub(crate) fn from_decrypted_message(
228        decrypted_message: DecryptedMessage,
229        credential: Credential,
230        sender_pk: OpenMlsSignaturePublicKey,
231        sender_context: Option<SenderContext>,
232    ) -> Self {
233        UnverifiedMessage {
234            verifiable_content: decrypted_message.verifiable_content,
235            credential,
236            sender_pk,
237            sender_context,
238        }
239    }
240
241    /// Verify the [`UnverifiedMessage`]. Returns the [`AuthenticatedContent`]
242    /// and the internal [`Credential`].
243    pub(crate) fn verify(
244        self,
245        ciphersuite: Ciphersuite,
246        crypto: &impl OpenMlsCrypto,
247        protocol_version: ProtocolVersion,
248    ) -> Result<(AuthenticatedContent, Credential), ValidationError> {
249        let content: AuthenticatedContentIn = self
250            .verifiable_content
251            .verify(crypto, &self.sender_pk)
252            .map_err(|_| ValidationError::InvalidSignature)?;
253        // https://validation.openmls.tech/#valn1302
254        // https://validation.openmls.tech/#valn1304
255        let content =
256            content.validate(ciphersuite, crypto, self.sender_context, protocol_version)?;
257        Ok((content, self.credential))
258    }
259
260    /// Get the content type of the message.
261    pub(crate) fn content_type(&self) -> ContentType {
262        self.verifiable_content.content_type()
263    }
264}
265
266/// A message that has passed all syntax and semantics checks.
267#[derive(Debug)]
268pub struct ProcessedMessage {
269    group_id: GroupId,
270    epoch: GroupEpoch,
271    sender: Sender,
272    authenticated_data: Vec<u8>,
273    content: ProcessedMessageContent,
274    credential: Credential,
275}
276
277impl ProcessedMessage {
278    /// Create a new `ProcessedMessage`.
279    pub(crate) fn new(
280        group_id: GroupId,
281        epoch: GroupEpoch,
282        sender: Sender,
283        authenticated_data: Vec<u8>,
284        content: ProcessedMessageContent,
285        credential: Credential,
286    ) -> Self {
287        Self {
288            group_id,
289            epoch,
290            sender,
291            authenticated_data,
292            content,
293            credential,
294        }
295    }
296
297    /// Returns the group ID of the message.
298    pub fn group_id(&self) -> &GroupId {
299        &self.group_id
300    }
301
302    /// Returns the epoch of the message.
303    pub fn epoch(&self) -> GroupEpoch {
304        self.epoch
305    }
306
307    /// Returns the sender of the message.
308    pub fn sender(&self) -> &Sender {
309        &self.sender
310    }
311
312    /// Returns the additional authenticated data (AAD) of the message.
313    pub fn aad(&self) -> &[u8] {
314        &self.authenticated_data
315    }
316
317    /// Returns the content of the message.
318    pub fn content(&self) -> &ProcessedMessageContent {
319        &self.content
320    }
321
322    /// Returns the content of the message and consumes the message.
323    pub fn into_content(self) -> ProcessedMessageContent {
324        self.content
325    }
326
327    /// Returns the credential of the message.
328    pub fn credential(&self) -> &Credential {
329        &self.credential
330    }
331}
332
333/// Content of a processed message.
334///
335/// See the content variants' documentation for more information.
336/// [`StagedCommit`] and [`QueuedProposal`] can be inspected for authorization purposes.
337#[derive(Debug)]
338pub enum ProcessedMessageContent {
339    /// An application message.
340    ///
341    /// The [`ApplicationMessage`] contains a vector of bytes that can be used right-away.
342    ApplicationMessage(ApplicationMessage),
343    /// A standalone proposal.
344    ///
345    /// The [`QueuedProposal`] can be inspected for authorization purposes by the application.
346    /// If the proposal is deemed to be allowed, it should be added to the group's proposal
347    /// queue using [`MlsGroup::store_pending_proposal()`](crate::group::mls_group::MlsGroup::store_pending_proposal()).
348    ProposalMessage(Box<QueuedProposal>),
349    /// An [external join proposal](crate::prelude::JoinProposal) sent by a
350    /// [NewMemberProposal](crate::prelude::Sender::NewMemberProposal) sender which is outside the group.
351    ///
352    /// Since this originates from a party outside the group, the [`QueuedProposal`] SHOULD be
353    /// inspected for authorization purposes by the application. If the proposal is deemed to be
354    /// allowed, it should be added to the group's proposal queue using
355    /// [`MlsGroup::store_pending_proposal()`](crate::group::mls_group::MlsGroup::store_pending_proposal()).
356    ExternalJoinProposalMessage(Box<QueuedProposal>),
357    /// A Commit message.
358    ///
359    /// The [`StagedCommit`] can be inspected for authorization purposes by the application.
360    /// If the type of the commit and the proposals it covers are deemed to be allowed,
361    /// the commit should be merged into the group's state using
362    /// [`MlsGroup::merge_staged_commit()`](crate::group::mls_group::MlsGroup::merge_staged_commit()).
363    StagedCommitMessage(Box<StagedCommit>),
364}
365
366/// Application message received through a [ProcessedMessage].
367#[derive(Debug, PartialEq, Eq)]
368pub struct ApplicationMessage {
369    bytes: Vec<u8>,
370}
371
372impl ApplicationMessage {
373    /// Create a new [ApplicationMessage].
374    pub(crate) fn new(bytes: Vec<u8>) -> Self {
375        Self { bytes }
376    }
377
378    /// Returns the inner bytes and consumes the [`ApplicationMessage`].
379    pub fn into_bytes(self) -> Vec<u8> {
380        self.bytes
381    }
382}