Skip to main content

openmls/framing/
private_message_in.rs

1use openmls_traits::crypto::OpenMlsCrypto;
2use openmls_traits::types::Ciphersuite;
3use tls_codec::{
4    Deserialize, Serialize, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
5};
6
7use super::{
8    codec::deserialize_ciphertext_content, mls_auth_content::FramedContentAuthData,
9    mls_auth_content_in::VerifiableAuthenticatedContentIn, mls_content_in::FramedContentBodyIn,
10};
11
12use crate::{
13    binary_tree::array_representation::LeafNodeIndex,
14    error::LibraryError,
15    framing::mls_content_in::FramedContentIn,
16    tree::{secret_tree::SecretType, sender_ratchet::SenderRatchetConfiguration},
17};
18
19use super::*;
20
21/// `PrivateMessage` is the framing struct for an encrypted `PublicMessage`.
22/// This message format is meant to be sent to and received from the Delivery
23/// Service.
24///
25/// ```c
26/// // draft-ietf-mls-protocol-17
27/// struct {
28///     opaque group_id<V>;
29///     uint64 epoch;
30///     ContentType content_type;
31///     opaque authenticated_data<V>;
32///     opaque encrypted_sender_data<V>;
33///     opaque ciphertext<V>;
34/// } PrivateMessage;
35/// ```
36#[derive(
37    Debug, PartialEq, Eq, Clone, TlsSerialize, TlsSize, TlsDeserialize, TlsDeserializeBytes,
38)]
39pub struct PrivateMessageIn {
40    group_id: GroupId,
41    epoch: GroupEpoch,
42    content_type: ContentType,
43    authenticated_data: VLBytes,
44    encrypted_sender_data: VLBytes,
45    ciphertext: VLBytes,
46}
47
48/// Return value of [`PrivateMessageIn::to_verifiable_content`].
49pub(crate) struct DecryptedContent {
50    pub(crate) verifiable: VerifiableAuthenticatedContentIn,
51    #[cfg(feature = "virtual-clients-draft")]
52    pub(crate) emulator_sender_leaf_index: Option<LeafNodeIndex>,
53}
54
55impl PrivateMessageIn {
56    /// Retrieve the additional authenticated data (AAD) from the [`PrivateMessageIn`].
57    ///
58    /// NOTE: This AAD is unverified.
59    pub fn aad(&self) -> &[u8] {
60        self.authenticated_data.as_slice()
61    }
62
63    /// Decrypt the sender data from this [`PrivateMessageIn`].
64    pub(crate) fn sender_data(
65        &self,
66        message_secrets: &MessageSecrets,
67        crypto: &impl OpenMlsCrypto,
68        ciphersuite: Ciphersuite,
69    ) -> Result<MlsSenderData, MessageDecryptionError> {
70        log::debug!("Decrypting PrivateMessage");
71        // Derive key from the key schedule using the ciphertext.
72        let sender_data_key = message_secrets
73            .sender_data_secret()
74            .derive_aead_key(crypto, ciphersuite, self.ciphertext.as_slice())
75            .map_err(LibraryError::unexpected_crypto_error)?;
76        // Derive initial nonce from the key schedule using the ciphertext.
77        let sender_data_nonce = message_secrets
78            .sender_data_secret()
79            .derive_aead_nonce(ciphersuite, crypto, self.ciphertext.as_slice())
80            .map_err(LibraryError::unexpected_crypto_error)?;
81        // Serialize sender data AAD
82        let mls_sender_data_aad =
83            MlsSenderDataAad::new(self.group_id.clone(), self.epoch, self.content_type);
84        let mls_sender_data_aad_bytes = mls_sender_data_aad
85            .tls_serialize_detached()
86            .map_err(LibraryError::missing_bound_check)?;
87        // Decrypt sender data
88        log_crypto!(
89            trace,
90            "Decryption key for sender data: {sender_data_key:x?}"
91        );
92        log_crypto!(trace, "Decryption of sender data mls_sender_data_aad_bytes: {mls_sender_data_aad_bytes:x?} - sender_data_nonce: {sender_data_nonce:x?}");
93        let sender_data_bytes = sender_data_key
94            .aead_open(
95                crypto,
96                self.encrypted_sender_data.as_slice(),
97                &mls_sender_data_aad_bytes,
98                &sender_data_nonce,
99            )
100            .map_err(|_| {
101                log::error!("Sender data decryption error");
102                MessageDecryptionError::AeadError
103            })?;
104        log::trace!("  Successfully decrypted sender data.");
105        MlsSenderData::tls_deserialize(&mut sender_data_bytes.as_slice())
106            .map_err(|_| MessageDecryptionError::MalformedContent)
107    }
108
109    /// Decrypt this [`PrivateMessage`] and return the
110    /// [`PrivateMessageContentIn`].
111    #[inline]
112    fn decrypt(
113        &self,
114        crypto: &impl OpenMlsCrypto,
115        ratchet_key: AeadKey,
116        ratchet_nonce: &AeadNonce,
117    ) -> Result<PrivateMessageContentIn, MessageDecryptionError> {
118        // Serialize content AAD
119        let private_message_content_aad_bytes = PrivateContentAad {
120            group_id: self.group_id.clone(),
121            epoch: self.epoch,
122            content_type: self.content_type,
123            authenticated_data: VLByteSlice(self.authenticated_data.as_slice()),
124        }
125        .tls_serialize_detached()
126        .map_err(LibraryError::missing_bound_check)?;
127        // Decrypt payload
128        log_crypto!(
129            trace,
130            "Decryption key for private message: {ratchet_key:x?}"
131        );
132        log_crypto!(trace, "Decryption of private message private_message_content_aad_bytes: {private_message_content_aad_bytes:x?} - ratchet_nonce: {ratchet_nonce:x?}");
133        log::trace!("Decrypting ciphertext {:x?}", self.ciphertext);
134        let private_message_content_bytes = ratchet_key
135            .aead_open(
136                crypto,
137                self.ciphertext.as_slice(),
138                &private_message_content_aad_bytes,
139                ratchet_nonce,
140            )
141            .map_err(|_| {
142                log::error!("  Ciphertext decryption error");
143                MessageDecryptionError::AeadError
144            })?;
145        log_content!(
146            trace,
147            "  Successfully decrypted PublicMessage bytes: {:x?}",
148            private_message_content_bytes
149        );
150        deserialize_ciphertext_content(
151            &mut private_message_content_bytes.as_slice(),
152            self.content_type(),
153        )
154        .map_err(|_| MessageDecryptionError::MalformedContent)
155    }
156
157    /// This function decrypts a [`PrivateMessage`] into a
158    /// [`VerifiableAuthenticatedContent`]. In order to get an
159    /// [`FramedContent`] the result must be verified.
160    ///
161    /// When called with `emulator_ctx = Some(_)`, also inverts the
162    /// virtual-clients reuse guard to recover the sender's emulation-group
163    /// leaf index.
164    #[allow(clippy::too_many_arguments)]
165    pub(crate) fn to_verifiable_content(
166        &self,
167        ciphersuite: Ciphersuite,
168        crypto: &impl OpenMlsCrypto,
169        message_secrets: &mut MessageSecrets,
170        sender_index: LeafNodeIndex,
171        sender_ratchet_configuration: &SenderRatchetConfiguration,
172        sender_data: MlsSenderData,
173        #[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<
174            &crate::framing::private_message::EmulatorReuseGuardCtx<'_>,
175        >,
176    ) -> Result<DecryptedContent, MessageDecryptionError> {
177        let secret_type = SecretType::from(&self.content_type);
178        // Extract generation and key material for encryption
179        let (ratchet_key, ratchet_nonce) = message_secrets
180            .secret_tree_mut()
181            .secret_for_decryption(
182                ciphersuite,
183                crypto,
184                sender_index,
185                secret_type,
186                sender_data.generation,
187                sender_ratchet_configuration,
188            )
189            .map_err(|e| {
190                log::error!(
191                    "  Ciphertext generation out of bounds {}\n\t{e:?}",
192                    sender_data.generation
193                );
194                MessageDecryptionError::SecretTreeError(e)
195            })?;
196
197        // Reuse-guard inversion. Uses the pre-XOR ratchet nonce as the
198        // `key_schedule_nonce` input to `ExpandWithLabel`.
199        #[cfg(feature = "virtual-clients-draft")]
200        let emulator_sender_leaf_index = if let Some(ctx) = emulator_ctx {
201            let prp_key = ctx.reuse_guard_secret.derive_prp_key(
202                crypto,
203                ctx.emulation_ciphersuite,
204                ratchet_nonce.raw_bytes(),
205            )?;
206            let x = crypto
207                .ff1_aes128_decrypt(
208                    &prp_key,
209                    u32::from_be_bytes(sender_data.reuse_guard.bytes()),
210                )
211                .map_err(|e| {
212                    log::error!("vc: FF1 inversion of reuse_guard failed: {e:?}");
213                    crate::components::vc_derivation_info::VirtualClientsError::CryptoError(
214                        openmls_traits::types::CryptoError::CryptoLibraryError,
215                    )
216                })?;
217            let n_e = u64::from(ctx.emulation_group_size.leaf_count());
218            if n_e == 0 {
219                log::error!("vc: emulation_group_size is zero (corrupt state)");
220                return Err(LibraryError::custom(
221                    "EmulationEpochState has zero emulation_group_size",
222                )
223                .into());
224            }
225            Some(LeafNodeIndex::new((u64::from(x) % n_e) as u32))
226        } else {
227            None
228        };
229
230        // Prepare the nonce by xoring with the reuse guard.
231        let prepared_nonce = ratchet_nonce.xor_with_reuse_guard(&sender_data.reuse_guard);
232        let private_message_content = self.decrypt(crypto, ratchet_key, &prepared_nonce)?;
233
234        // Extract sender. The sender type is always of type Member for PrivateMessage.
235        let sender = Sender::from_sender_data(sender_data);
236        log_content!(
237            trace,
238            "  Successfully decoded PublicMessage with: {:x?}",
239            private_message_content.content
240        );
241
242        let verifiable = VerifiableAuthenticatedContentIn::new(
243            WireFormat::PrivateMessage,
244            FramedContentIn {
245                group_id: self.group_id.clone(),
246                epoch: self.epoch,
247                sender,
248                authenticated_data: self.authenticated_data.clone(),
249                body: private_message_content.content,
250            },
251            Some(message_secrets.serialized_context().to_vec()),
252            private_message_content.auth,
253        );
254        Ok(DecryptedContent {
255            verifiable,
256            #[cfg(feature = "virtual-clients-draft")]
257            emulator_sender_leaf_index,
258        })
259    }
260
261    /// Get the `group_id` in the `PrivateMessage`.
262    pub fn group_id(&self) -> &GroupId {
263        &self.group_id
264    }
265
266    /// Get the `epoch` in the `PrivateMessage`.
267    pub fn epoch(&self) -> GroupEpoch {
268        self.epoch
269    }
270
271    /// Get the `content_type` in the `PrivateMessage`.
272    pub fn content_type(&self) -> ContentType {
273        self.content_type
274    }
275
276    /// Set the ciphertext.
277    #[cfg(test)]
278    pub(crate) fn set_ciphertext(&mut self, ciphertext: Vec<u8>) {
279        self.ciphertext = ciphertext.into();
280    }
281}
282
283// === Helper structs ===
284
285/// PrivateMessageContent
286///
287/// ```c
288/// // draft-ietf-mls-protocol-17
289/// struct {
290///     select (PrivateMessage.content_type) {
291///         case application:
292///           opaque application_data<V>;
293///
294///         case proposal:
295///           Proposal proposal;
296///
297///         case commit:
298///           Commit commit;
299///     }
300///
301///     FramedContentAuthData auth;
302///     opaque padding[length_of_padding];
303/// } PrivateMessageContent;
304/// ```
305#[derive(Debug, Clone)]
306pub(crate) struct PrivateMessageContentIn {
307    // The `content` field is serialized and deserialized manually without the
308    // `content_type`, which is not part of the struct as per MLS spec. See the
309    // implementation of `TlsSerialize` for `PrivateMessageContentIn`, as well
310    // as `deserialize_ciphertext_content`.
311    pub(crate) content: FramedContentBodyIn,
312    pub(crate) auth: FramedContentAuthData,
313}
314
315// The following `From` implementation( breaks abstraction layers and MUST
316// NOT be made available outside of tests or "test-utils".
317#[cfg(any(feature = "test-utils", test))]
318impl From<PrivateMessageIn> for PrivateMessage {
319    fn from(value: PrivateMessageIn) -> Self {
320        Self {
321            group_id: value.group_id,
322            epoch: value.epoch,
323            content_type: value.content_type,
324            authenticated_data: value.authenticated_data,
325            encrypted_sender_data: value.encrypted_sender_data,
326            ciphertext: value.ciphertext,
327        }
328    }
329}
330
331#[cfg(any(feature = "test-utils", test))]
332impl From<PrivateMessage> for PrivateMessageIn {
333    fn from(value: PrivateMessage) -> Self {
334        Self {
335            group_id: value.group_id,
336            epoch: value.epoch,
337            content_type: value.content_type,
338            authenticated_data: value.authenticated_data,
339            encrypted_sender_data: value.encrypted_sender_data,
340            ciphertext: value.ciphertext,
341        }
342    }
343}