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
12#[cfg(feature = "virtual-clients-draft")]
13use crate::binary_tree::array_representation::LeafNodeIndex;
14use crate::{
15    error::LibraryError, framing::mls_content_in::FramedContentIn,
16    tree::sender_ratchet::RatchetKeyMaterial,
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`] with the sender's ratchet
158    /// key material into a [`VerifiableAuthenticatedContent`]. In order to get
159    /// an [`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    pub(crate) fn to_verifiable_content(
165        &self,
166        crypto: &impl OpenMlsCrypto,
167        message_secrets: &MessageSecrets,
168        ratchet_key_material: RatchetKeyMaterial,
169        sender_data: MlsSenderData,
170        #[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<
171            &crate::framing::private_message::EmulatorReuseGuardCtx<'_>,
172        >,
173    ) -> Result<DecryptedContent, MessageDecryptionError> {
174        let (ratchet_key, ratchet_nonce) = ratchet_key_material;
175
176        // Reuse-guard inversion. Uses the pre-XOR ratchet nonce as the
177        // `key_schedule_nonce` input to `ExpandWithLabel`.
178        #[cfg(feature = "virtual-clients-draft")]
179        let emulator_sender_leaf_index = if let Some(ctx) = emulator_ctx {
180            let prp_key = ctx.reuse_guard_secret.derive_prp_key(
181                crypto,
182                ctx.emulation_ciphersuite,
183                ratchet_nonce.raw_bytes(),
184            )?;
185            let x = crypto
186                .ff1_aes128_decrypt(
187                    &prp_key,
188                    u32::from_be_bytes(sender_data.reuse_guard.bytes()),
189                )
190                .map_err(|e| {
191                    log::error!("vc: FF1 inversion of reuse_guard failed: {e:?}");
192                    crate::components::vc_derivation_info::VirtualClientsError::CryptoError(
193                        openmls_traits::types::CryptoError::CryptoLibraryError,
194                    )
195                })?;
196            let n_e = u64::from(ctx.emulation_group_size.leaf_count());
197            if n_e == 0 {
198                log::error!("vc: emulation_group_size is zero (corrupt state)");
199                return Err(LibraryError::custom(
200                    "VcDerivationEpochState has zero emulation_group_size",
201                )
202                .into());
203            }
204            Some(LeafNodeIndex::new((u64::from(x) % n_e) as u32))
205        } else {
206            None
207        };
208
209        // Prepare the nonce by xoring with the reuse guard.
210        let prepared_nonce = ratchet_nonce.xor_with_reuse_guard(&sender_data.reuse_guard);
211        let private_message_content = self.decrypt(crypto, ratchet_key, &prepared_nonce)?;
212
213        // Extract sender. The sender type is always of type Member for PrivateMessage.
214        let sender = Sender::from_sender_data(sender_data);
215        log_content!(
216            trace,
217            "  Successfully decoded PublicMessage with: {:x?}",
218            private_message_content.content
219        );
220
221        let verifiable = VerifiableAuthenticatedContentIn::new(
222            WireFormat::PrivateMessage,
223            FramedContentIn {
224                group_id: self.group_id.clone(),
225                epoch: self.epoch,
226                sender,
227                authenticated_data: self.authenticated_data.clone(),
228                body: private_message_content.content,
229            },
230            Some(message_secrets.serialized_context().to_vec()),
231            private_message_content.auth,
232        );
233        Ok(DecryptedContent {
234            verifiable,
235            #[cfg(feature = "virtual-clients-draft")]
236            emulator_sender_leaf_index,
237        })
238    }
239
240    /// Get the `group_id` in the `PrivateMessage`.
241    pub fn group_id(&self) -> &GroupId {
242        &self.group_id
243    }
244
245    /// Get the `epoch` in the `PrivateMessage`.
246    pub fn epoch(&self) -> GroupEpoch {
247        self.epoch
248    }
249
250    /// Get the `content_type` in the `PrivateMessage`.
251    pub fn content_type(&self) -> ContentType {
252        self.content_type
253    }
254
255    /// Set the ciphertext.
256    #[cfg(test)]
257    pub(crate) fn set_ciphertext(&mut self, ciphertext: Vec<u8>) {
258        self.ciphertext = ciphertext.into();
259    }
260}
261
262// === Helper structs ===
263
264/// PrivateMessageContent
265///
266/// ```c
267/// // draft-ietf-mls-protocol-17
268/// struct {
269///     select (PrivateMessage.content_type) {
270///         case application:
271///           opaque application_data<V>;
272///
273///         case proposal:
274///           Proposal proposal;
275///
276///         case commit:
277///           Commit commit;
278///     }
279///
280///     FramedContentAuthData auth;
281///     opaque padding[length_of_padding];
282/// } PrivateMessageContent;
283/// ```
284#[derive(Debug, Clone)]
285pub(crate) struct PrivateMessageContentIn {
286    // The `content` field is serialized and deserialized manually without the
287    // `content_type`, which is not part of the struct as per MLS spec. See the
288    // implementation of `TlsSerialize` for `PrivateMessageContentIn`, as well
289    // as `deserialize_ciphertext_content`.
290    pub(crate) content: FramedContentBodyIn,
291    pub(crate) auth: FramedContentAuthData,
292}
293
294impl From<PrivateMessage> for PrivateMessageIn {
295    fn from(value: PrivateMessage) -> Self {
296        Self {
297            group_id: value.group_id,
298            epoch: value.epoch,
299            content_type: value.content_type,
300            authenticated_data: value.authenticated_data,
301            encrypted_sender_data: value.encrypted_sender_data,
302            ciphertext: value.ciphertext,
303        }
304    }
305}
306
307// The following `From` implementation( breaks abstraction layers and MUST
308// NOT be made available outside of tests or "test-utils".
309#[cfg(any(feature = "test-utils", test))]
310impl From<PrivateMessageIn> for PrivateMessage {
311    fn from(value: PrivateMessageIn) -> Self {
312        Self {
313            group_id: value.group_id,
314            epoch: value.epoch,
315            content_type: value.content_type,
316            authenticated_data: value.authenticated_data,
317            encrypted_sender_data: value.encrypted_sender_data,
318            ciphertext: value.ciphertext,
319        }
320    }
321}