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(Debug, PartialEq, Eq, Clone, TlsSerialize, TlsSize)]
30pub struct PrivateMessage {
31 pub(crate) group_id: GroupId,
32 pub(crate) epoch: GroupEpoch,
33 pub(crate) content_type: ContentType,
34 pub(crate) authenticated_data: VLBytes,
35 pub(crate) encrypted_sender_data: VLBytes,
36 pub(crate) ciphertext: VLBytes,
37}
38
39pub(crate) struct MlsMessageHeader {
40 pub(crate) group_id: GroupId,
41 pub(crate) epoch: GroupEpoch,
42 pub(crate) sender: LeafNodeIndex,
43}
44
45impl PrivateMessage {
46 #[cfg(test)]
47 pub(crate) fn new(
48 group_id: GroupId,
49 epoch: GroupEpoch,
50 content_type: ContentType,
51 authenticated_data: VLBytes,
52 encrypted_sender_data: VLBytes,
53 ciphertext: VLBytes,
54 ) -> Self {
55 Self {
56 group_id,
57 epoch,
58 content_type,
59 authenticated_data,
60 encrypted_sender_data,
61 ciphertext,
62 }
63 }
64
65 pub(crate) fn try_from_authenticated_content<T>(
70 crypto: &impl OpenMlsCrypto,
71 rand: &impl OpenMlsRand,
72 public_message: &AuthenticatedContent,
73 ciphersuite: Ciphersuite,
74 message_secrets: &mut MessageSecrets,
75 padding_size: usize,
76 ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
77 log::debug!("PrivateMessage::try_from_authenticated_content");
78 log::trace!(" ciphersuite: {}", ciphersuite);
79 if public_message.wire_format() != WireFormat::PrivateMessage {
81 return Err(MessageEncryptionError::WrongWireFormat);
82 }
83 Self::encrypt_content(
84 crypto,
85 rand,
86 None,
87 public_message,
88 ciphersuite,
89 message_secrets,
90 padding_size,
91 )
92 }
93
94 #[cfg(any(feature = "test-utils", test))]
95 pub(crate) fn encrypt_without_check<T>(
96 crypto: &impl OpenMlsCrypto,
97 rand: &impl OpenMlsRand,
98 public_message: &AuthenticatedContent,
99 ciphersuite: Ciphersuite,
100 message_secrets: &mut MessageSecrets,
101 padding_size: usize,
102 ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
103 Self::encrypt_content(
104 crypto,
105 rand,
106 None,
107 public_message,
108 ciphersuite,
109 message_secrets,
110 padding_size,
111 )
112 }
113
114 #[cfg(test)]
115 pub(crate) fn encrypt_with_different_header<T>(
116 crypto: &impl OpenMlsCrypto,
117 rand: &impl OpenMlsRand,
118 public_message: &AuthenticatedContent,
119 ciphersuite: Ciphersuite,
120 header: MlsMessageHeader,
121 message_secrets: &mut MessageSecrets,
122 padding_size: usize,
123 ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
124 Self::encrypt_content(
125 crypto,
126 rand,
127 Some(header),
128 public_message,
129 ciphersuite,
130 message_secrets,
131 padding_size,
132 )
133 }
134
135 fn encrypt_content<T>(
138 crypto: &impl OpenMlsCrypto,
139 rand: &impl OpenMlsRand,
140 test_header: Option<MlsMessageHeader>,
141 public_message: &AuthenticatedContent,
142 ciphersuite: Ciphersuite,
143 message_secrets: &mut MessageSecrets,
144 padding_size: usize,
145 ) -> Result<PrivateMessage, MessageEncryptionError<T>> {
146 let sender_index = if let Some(index) = public_message.sender().as_member() {
148 index
149 } else {
150 return Err(LibraryError::custom("Sender is not a member.").into());
151 };
152 let header = match test_header {
154 Some(header) if cfg!(any(feature = "test-utils", test)) => header,
155 _ => MlsMessageHeader {
156 group_id: public_message.group_id().clone(),
157 epoch: public_message.epoch(),
158 sender: sender_index,
159 },
160 };
161 let private_message_content_aad = PrivateContentAad {
163 group_id: header.group_id.clone(),
164 epoch: header.epoch,
165 content_type: public_message.content().content_type(),
166 authenticated_data: VLByteSlice(public_message.authenticated_data()),
167 };
168 let private_message_content_aad_bytes = private_message_content_aad
169 .tls_serialize_detached()
170 .map_err(LibraryError::missing_bound_check)?;
171 let secret_type = SecretType::from(&public_message.content().content_type());
173 let (generation, (ratchet_key, ratchet_nonce)) = message_secrets
174 .secret_tree_mut()
175 .secret_for_encryption(ciphersuite, crypto, sender_index, secret_type)?;
177 let reuse_guard: ReuseGuard =
179 ReuseGuard::try_from_random(rand).map_err(LibraryError::unexpected_crypto_error)?;
180 let prepared_nonce = ratchet_nonce.xor_with_reuse_guard(&reuse_guard);
182 log_crypto!(
184 trace,
185 "Encryption key for private message: {ratchet_key:x?}"
186 );
187 log_crypto!(trace, "Encryption of private message private_message_content_aad_bytes: {private_message_content_aad_bytes:x?} - ratchet_nonce: {prepared_nonce:x?}");
188 let ciphertext = ratchet_key
189 .aead_seal(
190 crypto,
191 &Self::encode_padded_ciphertext_content_detached(
192 public_message,
193 padding_size,
194 ciphersuite.mac_length(),
195 )
196 .map_err(LibraryError::missing_bound_check)?,
197 &private_message_content_aad_bytes,
198 &prepared_nonce,
199 )
200 .map_err(LibraryError::unexpected_crypto_error)?;
201 log::trace!("Encrypted ciphertext {:x?}", ciphertext);
202 let sender_data_key = message_secrets
204 .sender_data_secret()
205 .derive_aead_key(crypto, ciphersuite, &ciphertext)
206 .map_err(LibraryError::unexpected_crypto_error)?;
207 let sender_data_nonce = message_secrets
209 .sender_data_secret()
210 .derive_aead_nonce(ciphersuite, crypto, &ciphertext)
211 .map_err(LibraryError::unexpected_crypto_error)?;
212 let mls_sender_data_aad = MlsSenderDataAad::new(
215 header.group_id.clone(),
216 header.epoch,
217 public_message.content().content_type(),
218 );
219 let mls_sender_data_aad_bytes = mls_sender_data_aad
221 .tls_serialize_detached()
222 .map_err(LibraryError::missing_bound_check)?;
223 let sender_data = MlsSenderData::from_sender(
224 header.sender,
226 generation,
227 reuse_guard,
228 );
229 log_crypto!(
231 trace,
232 "Encryption key for sender data: {sender_data_key:x?}"
233 );
234 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?}");
235 let encrypted_sender_data = sender_data_key
236 .aead_seal(
237 crypto,
238 &sender_data
239 .tls_serialize_detached()
240 .map_err(LibraryError::missing_bound_check)?,
241 &mls_sender_data_aad_bytes,
242 &sender_data_nonce,
243 )
244 .map_err(LibraryError::unexpected_crypto_error)?;
245 Ok(PrivateMessage {
246 group_id: header.group_id.clone(),
247 epoch: header.epoch,
248 content_type: public_message.content().content_type(),
249 authenticated_data: public_message.authenticated_data().into(),
250 encrypted_sender_data: encrypted_sender_data.into(),
251 ciphertext: ciphertext.into(),
252 })
253 }
254
255 #[cfg(test)]
257 pub(crate) fn is_handshake_message(&self) -> bool {
258 self.content_type.is_handshake_message()
259 }
260
261 fn encode_padded_ciphertext_content_detached(
263 authenticated_content: &AuthenticatedContent,
264 padding_size: usize,
265 mac_len: usize,
266 ) -> Result<Vec<u8>, tls_codec::Error> {
267 let plaintext_length = authenticated_content
268 .content()
269 .serialized_len_without_type()
270 + authenticated_content.auth.tls_serialized_len();
271
272 let padding_length = if padding_size > 0 {
273 let padding_offset = plaintext_length + mac_len;
276 (padding_size - (padding_offset % padding_size)) % padding_size
278 } else {
279 0
280 };
281
282 let buffer = &mut Vec::with_capacity(plaintext_length + padding_length);
284
285 authenticated_content
288 .content()
289 .serialize_without_type(buffer)?;
290 authenticated_content.auth.tls_serialize(buffer)?;
291 buffer
294 .write_all(&vec![0u8; padding_length])
295 .map_err(|_| Error::EncodingError("Failed to write padding.".into()))?;
296
297 Ok(buffer.to_vec())
298 }
299
300 #[cfg(test)]
302 pub(crate) fn ciphertext(&self) -> &[u8] {
303 self.ciphertext.as_slice()
304 }
305}
306
307#[derive(TlsSerialize, TlsSize)]
308pub(crate) struct PrivateContentAad<'a> {
309 pub(crate) group_id: GroupId,
310 pub(crate) epoch: GroupEpoch,
311 pub(crate) content_type: ContentType,
312 pub(crate) authenticated_data: VLByteSlice<'a>,
313}