Skip to main content

openmls/framing/
private_message.rs

1use openmls_traits::{crypto::OpenMlsCrypto, random::OpenMlsRand, types::Ciphersuite};
2use std::io::Write;
3use tls_codec::{Serialize, Size, TlsSerialize, TlsSize};
4
5use super::mls_auth_content::AuthenticatedContent;
6
7use crate::{
8    binary_tree::array_representation::LeafNodeIndex, error::LibraryError,
9    tree::secret_tree::SecretType,
10};
11
12use super::*;
13
14/// `PrivateMessage` is the framing struct for an encrypted `PublicMessage`.
15/// This message format is meant to be sent to and received from the Delivery
16/// Service.
17///
18/// ```c
19/// // draft-ietf-mls-protocol-17
20/// struct {
21///     opaque group_id<V>;
22///     uint64 epoch;
23///     ContentType content_type;
24///     opaque authenticated_data<V>;
25///     opaque encrypted_sender_data<V>;
26///     opaque ciphertext<V>;
27/// } PrivateMessage;
28/// ```
29#[derive(
30    Debug, PartialEq, Eq, Clone, TlsSerialize, TlsSize, serde::Serialize, serde::Deserialize,
31)]
32pub struct PrivateMessage {
33    pub(crate) group_id: GroupId,
34    pub(crate) epoch: GroupEpoch,
35    pub(crate) content_type: ContentType,
36    pub(crate) authenticated_data: VLBytes,
37    pub(crate) encrypted_sender_data: VLBytes,
38    pub(crate) ciphertext: VLBytes,
39}
40
41pub(crate) struct MlsMessageHeader {
42    pub(crate) group_id: GroupId,
43    pub(crate) epoch: GroupEpoch,
44    pub(crate) sender: LeafNodeIndex,
45}
46
47impl PrivateMessage {
48    #[cfg(test)]
49    pub(crate) fn new(
50        group_id: GroupId,
51        epoch: GroupEpoch,
52        content_type: ContentType,
53        authenticated_data: VLBytes,
54        encrypted_sender_data: VLBytes,
55        ciphertext: VLBytes,
56    ) -> Self {
57        Self {
58            group_id,
59            epoch,
60            content_type,
61            authenticated_data,
62            encrypted_sender_data,
63            ciphertext,
64        }
65    }
66
67    /// Try to create a new `PrivateMessage` from an `AuthenticatedContent`.
68    ///
69    /// TODO #1148: Refactor theses constructors to avoid test code in main and
70    /// to avoid validation using a special feature flag.
71    pub(crate) fn try_from_authenticated_content<T>(
72        crypto: &impl OpenMlsCrypto,
73        rand: &impl OpenMlsRand,
74        public_message: &AuthenticatedContent,
75        ciphersuite: Ciphersuite,
76        message_secrets: &mut MessageSecrets,
77        padding_size: usize,
78    ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
79        log::debug!("PrivateMessage::try_from_authenticated_content");
80        log::trace!("  ciphersuite: {ciphersuite}");
81        // Check the message has the correct wire format
82        if public_message.wire_format() != WireFormat::PrivateMessage {
83            return Err(MessageEncryptionError::WrongWireFormat);
84        }
85        Self::encrypt_content(
86            crypto,
87            rand,
88            None,
89            public_message,
90            ciphersuite,
91            message_secrets,
92            padding_size,
93        )
94    }
95
96    #[cfg(any(feature = "test-utils", test))]
97    pub(crate) fn encrypt_without_check<T>(
98        crypto: &impl OpenMlsCrypto,
99        rand: &impl OpenMlsRand,
100        public_message: &AuthenticatedContent,
101        ciphersuite: Ciphersuite,
102        message_secrets: &mut MessageSecrets,
103        padding_size: usize,
104    ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
105        Self::encrypt_content(
106            crypto,
107            rand,
108            None,
109            public_message,
110            ciphersuite,
111            message_secrets,
112            padding_size,
113        )
114    }
115
116    #[cfg(test)]
117    pub(crate) fn encrypt_with_different_header<T>(
118        crypto: &impl OpenMlsCrypto,
119        rand: &impl OpenMlsRand,
120        public_message: &AuthenticatedContent,
121        ciphersuite: Ciphersuite,
122        header: MlsMessageHeader,
123        message_secrets: &mut MessageSecrets,
124        padding_size: usize,
125    ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
126        Self::encrypt_content(
127            crypto,
128            rand,
129            Some(header),
130            public_message,
131            ciphersuite,
132            message_secrets,
133            padding_size,
134        )
135    }
136
137    /// Internal function to encrypt content. The extra message header is only used
138    /// for tests. Otherwise, the data from the given `AuthenticatedContent` is used.
139    fn encrypt_content<T>(
140        crypto: &impl OpenMlsCrypto,
141        rand: &impl OpenMlsRand,
142        test_header: Option<MlsMessageHeader>,
143        public_message: &AuthenticatedContent,
144        ciphersuite: Ciphersuite,
145        message_secrets: &mut MessageSecrets,
146        padding_size: usize,
147    ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
148        // https://validation.openmls.tech/#valn1305
149        let sender_index = if let Some(index) = public_message.sender().as_member() {
150            index
151        } else {
152            return Err(LibraryError::custom("Sender is not a member.").into());
153        };
154        // Take the provided header only if one is given and if this is indeed a test.
155        let header = match test_header {
156            Some(header) if cfg!(any(feature = "test-utils", test)) => header,
157            _ => MlsMessageHeader {
158                group_id: public_message.group_id().clone(),
159                epoch: public_message.epoch(),
160                sender: sender_index,
161            },
162        };
163        // Serialize the content AAD
164        let private_message_content_aad = PrivateContentAad {
165            group_id: header.group_id.clone(),
166            epoch: header.epoch,
167            content_type: public_message.content().content_type(),
168            authenticated_data: VLByteSlice(public_message.authenticated_data()),
169        };
170        let private_message_content_aad_bytes = private_message_content_aad
171            .tls_serialize_detached()
172            .map_err(LibraryError::missing_bound_check)?;
173        // Extract generation and key material for encryption
174        let secret_type = SecretType::from(&public_message.content().content_type());
175        let (generation, (ratchet_key, ratchet_nonce)) = message_secrets
176            .secret_tree_mut()
177            // Even in tests we want to use the real sender index, so we have a key to encrypt.
178            .secret_for_encryption(ciphersuite, crypto, sender_index, secret_type)?;
179        // Sample reuse guard uniformly at random.
180        let reuse_guard: ReuseGuard =
181            ReuseGuard::try_from_random(rand).map_err(LibraryError::unexpected_crypto_error)?;
182        // Prepare the nonce by xoring with the reuse guard.
183        let prepared_nonce = ratchet_nonce.xor_with_reuse_guard(&reuse_guard);
184        // Encrypt the payload
185        log_crypto!(
186            trace,
187            "Encryption key for private message: {ratchet_key:x?}"
188        );
189        log_crypto!(trace, "Encryption of private message private_message_content_aad_bytes: {private_message_content_aad_bytes:x?} - ratchet_nonce: {prepared_nonce:x?}");
190        let ciphertext = ratchet_key
191            .aead_seal(
192                crypto,
193                &Self::encode_padded_ciphertext_content_detached(
194                    public_message,
195                    padding_size,
196                    ciphersuite.mac_length(),
197                )
198                .map_err(LibraryError::missing_bound_check)?,
199                &private_message_content_aad_bytes,
200                &prepared_nonce,
201            )
202            .map_err(LibraryError::unexpected_crypto_error)?;
203        log::trace!("Encrypted ciphertext {ciphertext:x?}");
204        // Derive the sender data key from the key schedule using the ciphertext.
205        let sender_data_key = message_secrets
206            .sender_data_secret()
207            .derive_aead_key(crypto, ciphersuite, &ciphertext)
208            .map_err(LibraryError::unexpected_crypto_error)?;
209        // Derive initial nonce from the key schedule using the ciphertext.
210        let sender_data_nonce = message_secrets
211            .sender_data_secret()
212            .derive_aead_nonce(ciphersuite, crypto, &ciphertext)
213            .map_err(LibraryError::unexpected_crypto_error)?;
214        // Compute sender data nonce by xoring reuse guard and key schedule
215        // nonce as per spec.
216        let mls_sender_data_aad = MlsSenderDataAad::new(
217            header.group_id.clone(),
218            header.epoch,
219            public_message.content().content_type(),
220        );
221        // Serialize the sender data AAD
222        let mls_sender_data_aad_bytes = mls_sender_data_aad
223            .tls_serialize_detached()
224            .map_err(LibraryError::missing_bound_check)?;
225        let sender_data = MlsSenderData::from_sender(
226            // XXX: #106 This will fail for messages with a non-member sender.
227            header.sender,
228            generation,
229            reuse_guard,
230        );
231        // Encrypt the sender data
232        log_crypto!(
233            trace,
234            "Encryption key for sender data: {sender_data_key:x?}"
235        );
236        log_crypto!(trace, "Encryption of sender data mls_sender_data_aad_bytes: {mls_sender_data_aad_bytes:x?} - sender_data_nonce: {sender_data_nonce:x?}");
237        let encrypted_sender_data = sender_data_key
238            .aead_seal(
239                crypto,
240                &sender_data
241                    .tls_serialize_detached()
242                    .map_err(LibraryError::missing_bound_check)?,
243                &mls_sender_data_aad_bytes,
244                &sender_data_nonce,
245            )
246            .map_err(LibraryError::unexpected_crypto_error)?;
247        Ok(PrivateMessage {
248            group_id: header.group_id.clone(),
249            epoch: header.epoch,
250            content_type: public_message.content().content_type(),
251            authenticated_data: public_message.authenticated_data().into(),
252            encrypted_sender_data: encrypted_sender_data.into(),
253            ciphertext: ciphertext.into(),
254        })
255    }
256
257    /// Returns `true` if this is a handshake message and `false` otherwise.
258    #[cfg(test)]
259    pub(crate) fn is_handshake_message(&self) -> bool {
260        self.content_type.is_handshake_message()
261    }
262
263    /// Encodes the `PrivateMessageContent` struct with padding.
264    fn encode_padded_ciphertext_content_detached(
265        authenticated_content: &AuthenticatedContent,
266        padding_size: usize,
267        mac_len: usize,
268    ) -> Result<Vec<u8>, tls_codec::Error> {
269        let plaintext_length = authenticated_content
270            .content()
271            .serialized_len_without_type()
272            + authenticated_content.auth.tls_serialized_len();
273
274        let padding_length = if padding_size > 0 {
275            // Calculate padding block size.
276            // Only the AEAD tag is added.
277            let padding_offset = plaintext_length + mac_len;
278            // Return padding block size
279            (padding_size - (padding_offset % padding_size)) % padding_size
280        } else {
281            0
282        };
283
284        // Persist all initial fields manually (avoids cloning them)
285        let buffer = &mut Vec::with_capacity(plaintext_length + padding_length);
286
287        // The `content` field is serialized without the `content_type`, which
288        // is not part of the struct as per MLS spec.
289        authenticated_content
290            .content()
291            .serialize_without_type(buffer)?;
292        authenticated_content.auth.tls_serialize(buffer)?;
293        // Note: The `tls_codec::Serialize` implementation for `&[u8]` prepends the length.
294        // We do not want this here and thus use the "raw" `write_all` method.
295        buffer
296            .write_all(&vec![0u8; padding_length])
297            .map_err(|_| Error::EncodingError("Failed to write padding.".into()))?;
298
299        Ok(buffer.to_vec())
300    }
301
302    /// Get the cipher text bytes as slice.
303    #[cfg(test)]
304    pub(crate) fn ciphertext(&self) -> &[u8] {
305        self.ciphertext.as_slice()
306    }
307}
308
309#[derive(TlsSerialize, TlsSize)]
310pub(crate) struct PrivateContentAad<'a> {
311    pub(crate) group_id: GroupId,
312    pub(crate) epoch: GroupEpoch,
313    pub(crate) content_type: ContentType,
314    pub(crate) authenticated_data: VLByteSlice<'a>,
315}