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, tree::sender_ratchet::Generation,
10};
11
12#[cfg(feature = "virtual-clients-draft")]
13use crate::{
14 binary_tree::array_representation::TreeSize, components::vc_derivation_info::ReuseGuardSecret,
15};
16
17use super::*;
18
19#[cfg(feature = "virtual-clients-draft")]
22pub(crate) struct EmulatorReuseGuardCtx<'a> {
23 pub(crate) reuse_guard_secret: &'a ReuseGuardSecret,
24 pub(crate) emulation_ciphersuite: Ciphersuite,
25 pub(crate) emulation_group_size: TreeSize,
26 pub(crate) emulation_leaf_index: LeafNodeIndex,
27}
28
29#[derive(Debug)]
32pub(crate) struct EncryptionOutput {
33 pub(crate) generation: Generation,
38 pub(crate) private_message: PrivateMessage,
40 #[cfg(feature = "virtual-clients-draft")]
48 pub(crate) generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
49}
50
51#[derive(
67 Debug, PartialEq, Eq, Clone, TlsSerialize, TlsSize, serde::Serialize, serde::Deserialize,
68)]
69pub struct PrivateMessage {
70 pub(crate) group_id: GroupId,
71 pub(crate) epoch: GroupEpoch,
72 pub(crate) content_type: ContentType,
73 pub(crate) authenticated_data: VLBytes,
74 pub(crate) encrypted_sender_data: VLBytes,
75 pub(crate) ciphertext: VLBytes,
76}
77
78pub(crate) struct MlsMessageHeader {
79 pub(crate) group_id: GroupId,
80 pub(crate) epoch: GroupEpoch,
81 pub(crate) sender: LeafNodeIndex,
82}
83
84impl PrivateMessage {
85 pub fn group_id(&self) -> &GroupId {
87 &self.group_id
88 }
89
90 pub fn epoch(&self) -> GroupEpoch {
92 self.epoch
93 }
94
95 pub fn content_type(&self) -> ContentType {
97 self.content_type
98 }
99
100 #[cfg(test)]
101 pub(crate) fn new(
102 group_id: GroupId,
103 epoch: GroupEpoch,
104 content_type: ContentType,
105 authenticated_data: VLBytes,
106 encrypted_sender_data: VLBytes,
107 ciphertext: VLBytes,
108 ) -> Self {
109 Self {
110 group_id,
111 epoch,
112 content_type,
113 authenticated_data,
114 encrypted_sender_data,
115 ciphertext,
116 }
117 }
118
119 pub(crate) fn try_from_authenticated_content<T>(
124 crypto: &impl OpenMlsCrypto,
125 rand: &impl OpenMlsRand,
126 public_message: &AuthenticatedContent,
127 ciphersuite: Ciphersuite,
128 message_secrets: &mut MessageSecrets,
129 padding_size: usize,
130 #[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<&EmulatorReuseGuardCtx<'_>>,
131 ) -> Result<EncryptionOutput, MessageEncryptionError<T>> {
132 log::debug!("PrivateMessage::try_from_authenticated_content");
133 log::trace!(" ciphersuite: {ciphersuite}");
134 if public_message.wire_format() != WireFormat::PrivateMessage {
136 return Err(MessageEncryptionError::WrongWireFormat);
137 }
138 Self::encrypt_content(
139 crypto,
140 rand,
141 None,
142 public_message,
143 ciphersuite,
144 message_secrets,
145 padding_size,
146 #[cfg(feature = "virtual-clients-draft")]
147 emulator_ctx,
148 )
149 }
150
151 #[cfg(any(feature = "test-utils", test))]
152 pub(crate) fn encrypt_without_check<T>(
153 crypto: &impl OpenMlsCrypto,
154 rand: &impl OpenMlsRand,
155 public_message: &AuthenticatedContent,
156 ciphersuite: Ciphersuite,
157 message_secrets: &mut MessageSecrets,
158 padding_size: usize,
159 ) -> Result<EncryptionOutput, MessageEncryptionError<T>> {
160 Self::encrypt_content(
161 crypto,
162 rand,
163 None,
164 public_message,
165 ciphersuite,
166 message_secrets,
167 padding_size,
168 #[cfg(feature = "virtual-clients-draft")]
169 None,
170 )
171 }
172
173 #[cfg(test)]
174 pub(crate) fn encrypt_with_different_header<T>(
175 crypto: &impl OpenMlsCrypto,
176 rand: &impl OpenMlsRand,
177 public_message: &AuthenticatedContent,
178 ciphersuite: Ciphersuite,
179 header: MlsMessageHeader,
180 message_secrets: &mut MessageSecrets,
181 padding_size: usize,
182 ) -> Result<EncryptionOutput, MessageEncryptionError<T>> {
183 Self::encrypt_content(
184 crypto,
185 rand,
186 Some(header),
187 public_message,
188 ciphersuite,
189 message_secrets,
190 padding_size,
191 #[cfg(feature = "virtual-clients-draft")]
192 None,
193 )
194 }
195
196 #[allow(clippy::too_many_arguments)]
199 fn encrypt_content<T>(
200 crypto: &impl OpenMlsCrypto,
201 rand: &impl OpenMlsRand,
202 test_header: Option<MlsMessageHeader>,
203 public_message: &AuthenticatedContent,
204 ciphersuite: Ciphersuite,
205 message_secrets: &mut MessageSecrets,
206 padding_size: usize,
207 #[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<&EmulatorReuseGuardCtx<'_>>,
208 ) -> Result<EncryptionOutput, MessageEncryptionError<T>> {
209 let sender_index = if let Some(index) = public_message.sender().as_member() {
211 index
212 } else {
213 return Err(LibraryError::custom("Sender is not a member.").into());
214 };
215 let header = match test_header {
217 Some(header) if cfg!(any(feature = "test-utils", test)) => header,
218 _ => MlsMessageHeader {
219 group_id: public_message.group_id().clone(),
220 epoch: public_message.epoch(),
221 sender: sender_index,
222 },
223 };
224 let private_message_content_aad = PrivateContentAad {
226 group_id: header.group_id.clone(),
227 epoch: header.epoch,
228 content_type: public_message.content().content_type(),
229 authenticated_data: VLByteSlice(public_message.authenticated_data()),
230 };
231 let private_message_content_aad_bytes = private_message_content_aad
232 .tls_serialize_detached()
233 .map_err(LibraryError::missing_bound_check)?;
234 let secret_type = SecretType::from(&public_message.content().content_type());
236 let (generation, (ratchet_key, ratchet_nonce)) = message_secrets
237 .secret_tree_mut()
238 .secret_for_encryption(ciphersuite, crypto, sender_index, secret_type)?;
240 #[cfg(feature = "virtual-clients-draft")]
243 let reuse_guard: ReuseGuard = if let Some(ctx) = emulator_ctx {
244 ReuseGuard::for_emulator_sender(
245 crypto,
246 rand,
247 ctx.reuse_guard_secret,
248 ctx.emulation_ciphersuite,
249 &ratchet_nonce,
250 ctx.emulation_leaf_index,
251 ctx.emulation_group_size,
252 )
253 .map_err(|e| match e {
254 ReuseGuardDerivationError::VirtualClients(inner) => {
255 MessageEncryptionError::VirtualClientsError(inner)
256 }
257 ReuseGuardDerivationError::Library(inner) => {
258 MessageEncryptionError::LibraryError(inner)
259 }
260 })?
261 } else {
262 ReuseGuard::try_from_random(rand).map_err(LibraryError::unexpected_crypto_error)?
263 };
264 #[cfg(not(feature = "virtual-clients-draft"))]
265 let reuse_guard: ReuseGuard =
266 ReuseGuard::try_from_random(rand).map_err(LibraryError::unexpected_crypto_error)?;
267 let prepared_nonce = ratchet_nonce.xor_with_reuse_guard(&reuse_guard);
269 log_crypto!(
271 trace,
272 "Encryption key for private message: {ratchet_key:x?}"
273 );
274 log_crypto!(trace, "Encryption of private message private_message_content_aad_bytes: {private_message_content_aad_bytes:x?} - ratchet_nonce: {prepared_nonce:x?}");
275 let ciphertext = ratchet_key
276 .aead_seal(
277 crypto,
278 &Self::encode_padded_ciphertext_content_detached(
279 public_message,
280 padding_size,
281 ciphersuite.mac_length(),
282 )
283 .map_err(LibraryError::missing_bound_check)?,
284 &private_message_content_aad_bytes,
285 &prepared_nonce,
286 )
287 .map_err(LibraryError::unexpected_crypto_error)?;
288 log::trace!("Encrypted ciphertext {ciphertext:x?}");
289 let sender_data_key = message_secrets
291 .sender_data_secret()
292 .derive_aead_key(crypto, ciphersuite, &ciphertext)
293 .map_err(LibraryError::unexpected_crypto_error)?;
294 let sender_data_nonce = message_secrets
296 .sender_data_secret()
297 .derive_aead_nonce(ciphersuite, crypto, &ciphertext)
298 .map_err(LibraryError::unexpected_crypto_error)?;
299 let mls_sender_data_aad = MlsSenderDataAad::new(
302 header.group_id.clone(),
303 header.epoch,
304 public_message.content().content_type(),
305 );
306 let mls_sender_data_aad_bytes = mls_sender_data_aad
308 .tls_serialize_detached()
309 .map_err(LibraryError::missing_bound_check)?;
310 let sender_data = MlsSenderData::from_sender(
311 header.sender,
313 generation,
314 reuse_guard,
315 );
316 log_crypto!(
318 trace,
319 "Encryption key for sender data: {sender_data_key:x?}"
320 );
321 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?}");
322 let encrypted_sender_data = sender_data_key
323 .aead_seal(
324 crypto,
325 &sender_data
326 .tls_serialize_detached()
327 .map_err(LibraryError::missing_bound_check)?,
328 &mls_sender_data_aad_bytes,
329 &sender_data_nonce,
330 )
331 .map_err(LibraryError::unexpected_crypto_error)?;
332 let private_message = PrivateMessage {
333 group_id: header.group_id.clone(),
334 epoch: header.epoch,
335 content_type: public_message.content().content_type(),
336 authenticated_data: public_message.authenticated_data().into(),
337 encrypted_sender_data: encrypted_sender_data.into(),
338 ciphertext: ciphertext.into(),
339 };
340 Ok(EncryptionOutput {
341 generation,
342 private_message,
343 #[cfg(feature = "virtual-clients-draft")]
344 generation_id: None,
345 })
346 }
347
348 #[cfg(test)]
350 pub(crate) fn is_handshake_message(&self) -> bool {
351 self.content_type.is_handshake_message()
352 }
353
354 fn encode_padded_ciphertext_content_detached(
356 authenticated_content: &AuthenticatedContent,
357 padding_size: usize,
358 mac_len: usize,
359 ) -> Result<Vec<u8>, tls_codec::Error> {
360 let plaintext_length = authenticated_content
361 .content()
362 .serialized_len_without_type()
363 + authenticated_content.auth.tls_serialized_len();
364
365 let padding_length = if padding_size > 0 {
366 let padding_offset = plaintext_length + mac_len;
369 (padding_size - (padding_offset % padding_size)) % padding_size
371 } else {
372 0
373 };
374
375 let buffer = &mut Vec::with_capacity(plaintext_length + padding_length);
377
378 authenticated_content
381 .content()
382 .serialize_without_type(buffer)?;
383 authenticated_content.auth.tls_serialize(buffer)?;
384 buffer
387 .write_all(&vec![0u8; padding_length])
388 .map_err(|_| Error::EncodingError("Failed to write padding.".into()))?;
389
390 Ok(buffer.to_vec())
391 }
392
393 #[cfg(test)]
395 pub(crate) fn ciphertext(&self) -> &[u8] {
396 self.ciphertext.as_slice()
397 }
398}
399
400#[derive(TlsSerialize, TlsSize)]
401pub(crate) struct PrivateContentAad<'a> {
402 pub(crate) group_id: GroupId,
403 pub(crate) epoch: GroupEpoch,
404 pub(crate) content_type: ContentType,
405 pub(crate) authenticated_data: VLByteSlice<'a>,
406}