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#[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 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 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 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 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 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 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 let secret_type = SecretType::from(&public_message.content().content_type());
175 let (generation, (ratchet_key, ratchet_nonce)) = message_secrets
176 .secret_tree_mut()
177 .secret_for_encryption(ciphersuite, crypto, sender_index, secret_type)?;
179 let reuse_guard: ReuseGuard =
181 ReuseGuard::try_from_random(rand).map_err(LibraryError::unexpected_crypto_error)?;
182 let prepared_nonce = ratchet_nonce.xor_with_reuse_guard(&reuse_guard);
184 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 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 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 let mls_sender_data_aad = MlsSenderDataAad::new(
217 header.group_id.clone(),
218 header.epoch,
219 public_message.content().content_type(),
220 );
221 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 header.sender,
228 generation,
229 reuse_guard,
230 );
231 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 #[cfg(test)]
259 pub(crate) fn is_handshake_message(&self) -> bool {
260 self.content_type.is_handshake_message()
261 }
262
263 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 let padding_offset = plaintext_length + mac_len;
278 (padding_size - (padding_offset % padding_size)) % padding_size
280 } else {
281 0
282 };
283
284 let buffer = &mut Vec::with_capacity(plaintext_length + padding_length);
286
287 authenticated_content
290 .content()
291 .serialize_without_type(buffer)?;
292 authenticated_content.auth.tls_serialize(buffer)?;
293 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 #[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}