Skip to main content

openmls/test_utils/frankenstein/
framing.rs

1use openmls_traits::{crypto::OpenMlsCrypto, signatures::Signer, types::Ciphersuite};
2use tls_codec::*;
3
4use crate::{
5    binary_tree::LeafNodeIndex,
6    extensions::SenderExtensionIndex,
7    framing::{
8        mls_content::{AuthenticatedContentTbm, FramedContentBody, FramedContentTbs},
9        mls_content_in::FramedContentBodyIn,
10        MlsMessageIn, MlsMessageOut, PrivateMessage, PrivateMessageIn, PublicMessage,
11        PublicMessageIn, Sender, WireFormat,
12    },
13    group::GroupContext,
14    messages::{ConfirmationTag, GroupSecrets, PathSecret, Welcome},
15    prelude_test::signable::Signable,
16    schedule::{ConfirmationKey, JoinerSecret, MembershipKey},
17    test_utils::frankenstein::{FrankenHpkeCiphertext, FrankenPreSharedKeyId},
18};
19
20use super::{
21    commit::FrankenCommit,
22    compute_membership_tag,
23    group_info::{FrankenGroupContext, FrankenGroupInfo},
24    sign_with_label, FrankenKeyPackage, FrankenProposal,
25};
26
27#[derive(
28    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
29)]
30pub struct FrankenMlsMessage {
31    pub version: u16,
32    pub body: FrankenMlsMessageBody,
33}
34
35#[allow(clippy::large_enum_variant)]
36#[derive(
37    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
38)]
39#[repr(u16)]
40pub enum FrankenMlsMessageBody {
41    #[tls_codec(discriminant = 1)]
42    PublicMessage(FrankenPublicMessage),
43    #[tls_codec(discriminant = 2)]
44    PrivateMessage(FrankenPrivateMessage),
45    #[tls_codec(discriminant = 3)]
46    Welcome(FrankenWelcome),
47    #[tls_codec(discriminant = 4)]
48    GroupInfo(FrankenGroupInfo),
49    #[tls_codec(discriminant = 5)]
50    KeyPackage(FrankenKeyPackage),
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct FrankenPublicMessage {
55    pub content: FrankenFramedContent,
56    pub auth: FrankenFramedContentAuthData,
57    pub membership_tag: Option<VLBytes>,
58}
59
60impl tls_codec::Size for FrankenPublicMessage {
61    fn tls_serialized_len(&self) -> usize {
62        let tag_len = self
63            .membership_tag
64            .as_ref()
65            .map_or(0, |tag| tag.tls_serialized_len());
66
67        self.content.tls_serialized_len() + self.auth.tls_serialized_len() + tag_len
68    }
69}
70
71impl Deserialize for FrankenPublicMessage {
72    fn tls_deserialize<R: std::io::prelude::Read>(bytes: &mut R) -> Result<Self, Error>
73    where
74        Self: Sized,
75    {
76        let content = FrankenFramedContent::tls_deserialize(bytes)?;
77        let auth = if matches!(content.body, FrankenFramedContentBody::Commit(_)) {
78            FrankenFramedContentAuthData::tls_deserialize_with_tag(bytes)?
79        } else {
80            FrankenFramedContentAuthData::tls_deserialize_without_tag(bytes)?
81        };
82
83        let membership_tag = if matches!(content.sender, FrankenSender::Member(_)) {
84            Some(VLBytes::tls_deserialize(bytes)?)
85        } else {
86            None
87        };
88
89        Ok(Self {
90            content,
91            auth,
92            membership_tag,
93        })
94    }
95}
96
97impl DeserializeBytes for FrankenPublicMessage {
98    fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error>
99    where
100        Self: Sized,
101    {
102        let (content, bytes) = FrankenFramedContent::tls_deserialize_bytes(bytes)?;
103        let (auth, bytes) = match content.body {
104            FrankenFramedContentBody::Commit(_) => {
105                FrankenFramedContentAuthData::tls_deserialize_bytes_with_tag(bytes)
106            }
107            _ => FrankenFramedContentAuthData::tls_deserialize_bytes_without_tag(bytes),
108        }?;
109        let (membership_tag, bytes) = match content.sender {
110            FrankenSender::Member(_) => {
111                let (tag, bytes) = VLBytes::tls_deserialize_bytes(bytes)?;
112                (Some(tag), bytes)
113            }
114            _ => (None, bytes),
115        };
116
117        Ok((
118            Self {
119                content,
120                auth,
121                membership_tag,
122            },
123            bytes,
124        ))
125    }
126}
127
128impl Serialize for FrankenPublicMessage {
129    fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
130        let mut written = 0;
131        written += self.content.tls_serialize(writer)?;
132        written += self.auth.tls_serialize(writer)?;
133        if let Some(tag) = &self.membership_tag {
134            written += tag.tls_serialize(writer)?;
135        }
136
137        Ok(written)
138    }
139}
140
141impl FrankenPublicMessage {
142    /// auth builds a mostly(!) valid fake public message. However, it does not compute a correct
143    /// confirmation_tag. If the caller wants to process a message that requires a
144    /// confirmation_tag, they have two options:
145    ///
146    /// 1. build a valid tag themselves and provide it through the option
147    /// 2. provide a dummy tag and disable the verification of confirmation tags using
148    ///    [`crate::disable_confirmation_tag_verification`].
149    ///    NB: Usually, confirmation tag verification should be turned back on after the call that
150    ///    needs to be tricked!
151    pub(crate) fn auth(
152        provider: &impl crate::storage::OpenMlsProvider,
153        ciphersuite: openmls_traits::types::Ciphersuite,
154        signer: &impl Signer,
155        content: FrankenFramedContent,
156        group_context: Option<&FrankenGroupContext>,
157        membership_key: Option<&[u8]>,
158        confirmation_tag: Option<VLBytes>,
159    ) -> Self {
160        let version = 1; // MLS 1.0
161        let wire_format = 1; // PublicMessage
162
163        let franken_tbs = FrankenFramedContentTbs {
164            version,
165            wire_format,
166            content: &content,
167            group_context,
168        };
169
170        let auth = FrankenFramedContentAuthData::build(
171            signer,
172            version,
173            wire_format,
174            &content,
175            group_context,
176            confirmation_tag,
177        );
178
179        let tbm = FrankenAuthenticatedContentTbm {
180            content_tbs: franken_tbs,
181            auth: auth.clone(),
182        };
183
184        let membership_tag = membership_key.map(|membership_key| {
185            compute_membership_tag(provider.crypto(), ciphersuite, membership_key, &tbm)
186        });
187
188        FrankenPublicMessage {
189            content,
190            auth,
191            membership_tag,
192        }
193    }
194}
195
196#[derive(
197    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
198)]
199pub struct FrankenFramedContent {
200    pub group_id: VLBytes,
201    pub epoch: u64,
202    pub sender: FrankenSender,
203    pub authenticated_data: VLBytes,
204    pub body: FrankenFramedContentBody,
205}
206
207#[derive(
208    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
209)]
210#[repr(u8)]
211pub enum FrankenSender {
212    #[tls_codec(discriminant = 1)]
213    Member(u32),
214    External(u32),
215    NewMemberProposal,
216    NewMemberCommit,
217}
218
219#[derive(
220    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
221)]
222#[repr(u8)]
223pub enum FrankenFramedContentBody {
224    #[tls_codec(discriminant = 1)]
225    Application(VLBytes),
226    #[tls_codec(discriminant = 2)]
227    Proposal(FrankenProposal),
228    #[tls_codec(discriminant = 3)]
229    Commit(FrankenCommit),
230}
231
232#[derive(
233    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
234)]
235pub struct FrankenPrivateMessage {
236    pub group_id: VLBytes,
237    pub epoch: VLBytes,
238    pub content_type: FrankenContentType,
239    pub authenticated_data: VLBytes,
240    pub encrypted_sender_data: VLBytes,
241    pub ciphertext: VLBytes,
242}
243
244#[derive(
245    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
246)]
247pub struct FrankenWelcome {
248    pub cipher_suite: u16,
249    pub secrets: Vec<FrankenEncryptedGroupSecrets>,
250    pub encrypted_group_info: VLBytes,
251}
252
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct FrankenFramedContentAuthData {
255    pub signature: VLBytes,
256    pub confirmation_tag: Option<VLBytes>,
257}
258
259impl FrankenFramedContentAuthData {
260    pub fn tls_deserialize_with_tag<R: std::io::Read>(
261        bytes: &mut R,
262    ) -> Result<Self, tls_codec::Error> {
263        let signature = VLBytes::tls_deserialize(bytes)?;
264        let confirmation_tag = VLBytes::tls_deserialize(bytes)?;
265
266        Ok(Self {
267            signature,
268            confirmation_tag: Some(confirmation_tag),
269        })
270    }
271
272    pub fn tls_deserialize_bytes_with_tag(bytes: &[u8]) -> Result<(Self, &[u8]), tls_codec::Error> {
273        let (signature, bytes) = VLBytes::tls_deserialize_bytes(bytes)?;
274        let (confirmation_tag, bytes) = VLBytes::tls_deserialize_bytes(bytes)?;
275
276        Ok((
277            Self {
278                signature,
279                confirmation_tag: Some(confirmation_tag),
280            },
281            bytes,
282        ))
283    }
284
285    pub fn tls_deserialize_without_tag<R: std::io::Read>(
286        bytes: &mut R,
287    ) -> Result<Self, tls_codec::Error> {
288        let signature = VLBytes::tls_deserialize(bytes)?;
289
290        Ok(Self {
291            signature,
292            confirmation_tag: None,
293        })
294    }
295
296    pub fn tls_deserialize_bytes_without_tag(
297        bytes: &[u8],
298    ) -> Result<(Self, &[u8]), tls_codec::Error> {
299        let (signature, bytes) = VLBytes::tls_deserialize_bytes(bytes)?;
300
301        Ok((
302            Self {
303                signature,
304                confirmation_tag: None,
305            },
306            bytes,
307        ))
308    }
309}
310
311impl tls_codec::Size for FrankenFramedContentAuthData {
312    fn tls_serialized_len(&self) -> usize {
313        if let Some(tag) = &self.confirmation_tag {
314            self.signature.tls_serialized_len() + tag.tls_serialized_len()
315        } else {
316            self.signature.tls_serialized_len()
317        }
318    }
319}
320
321impl Serialize for FrankenFramedContentAuthData {
322    fn tls_serialize<W: std::io::prelude::Write>(&self, writer: &mut W) -> Result<usize, Error> {
323        let mut written = 0;
324        written += self.signature.tls_serialize(writer)?;
325        if let Some(confirmation_tag) = &self.confirmation_tag {
326            written += confirmation_tag.tls_serialize(writer)?;
327        }
328        Ok(written)
329    }
330}
331
332impl FrankenFramedContentAuthData {
333    pub fn build(
334        signer: &impl Signer,
335        version: u16,
336        wire_format: u16,
337        content: &FrankenFramedContent,
338        group_context: Option<&FrankenGroupContext>,
339        confirmation_tag: Option<VLBytes>,
340    ) -> Self {
341        let content_tbs = FrankenFramedContentTbs {
342            version,
343            wire_format,
344            content,
345            group_context,
346        };
347
348        let content_tbs_serialized = content_tbs.tls_serialize_detached().unwrap();
349
350        let signature =
351            sign_with_label(signer, b"FramedContentTBS", &content_tbs_serialized).into();
352
353        Self {
354            signature,
355            confirmation_tag,
356        }
357    }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct FrankenFramedContentTbs<'a> {
362    version: u16,
363    wire_format: u16,
364    content: &'a FrankenFramedContent,
365    group_context: Option<&'a FrankenGroupContext>,
366}
367
368impl tls_codec::Size for FrankenFramedContentTbs<'_> {
369    fn tls_serialized_len(&self) -> usize {
370        if let Some(ctx) = self.group_context {
371            4 + self.content.tls_serialized_len() + ctx.tls_serialized_len()
372        } else {
373            4 + self.content.tls_serialized_len()
374        }
375    }
376}
377
378impl Serialize for FrankenFramedContentTbs<'_> {
379    fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
380        writer.write_all(&self.version.to_be_bytes())?;
381        writer.write_all(&self.wire_format.to_be_bytes())?;
382
383        let mut written = 4; // contains the two u16 version and wire_format
384        written += self.content.tls_serialize(writer)?;
385        if let Some(group_context) = &self.group_context {
386            written += group_context.tls_serialize(writer)?;
387        }
388
389        Ok(written)
390    }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, TlsSerialize, TlsSize)]
394pub struct FrankenAuthenticatedContentTbm<'a> {
395    content_tbs: FrankenFramedContentTbs<'a>,
396    auth: FrankenFramedContentAuthData,
397}
398
399#[derive(
400    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
401)]
402#[repr(u8)]
403pub enum FrankenContentType {
404    Application = 1,
405    Proposal = 2,
406    Commit = 3,
407}
408
409#[derive(
410    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
411)]
412pub struct FrankenGroupSecrets {
413    pub joiner_secret: FrankenJoinerSecret,
414    pub path_secret: Option<FrankenPathSecret>,
415    pub psks: Vec<FrankenPreSharedKeyId>,
416}
417
418pub type FrankenPathSecret = VLBytes;
419pub type FrankenJoinerSecret = VLBytes;
420
421impl From<FrankenPathSecret> for PathSecret {
422    fn from(value: VLBytes) -> Self {
423        Self::tls_deserialize(&mut value.tls_serialize_detached().unwrap().as_slice()).unwrap()
424    }
425}
426
427#[derive(
428    Debug, Clone, PartialEq, Eq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
429)]
430pub struct FrankenEncryptedGroupSecrets {
431    pub new_member: VLBytes,
432    pub encrypted_group_secrets: FrankenHpkeCiphertext,
433}
434
435impl From<MlsMessageOut> for FrankenMlsMessage {
436    fn from(ln: MlsMessageOut) -> Self {
437        FrankenMlsMessage::tls_deserialize(&mut ln.tls_serialize_detached().unwrap().as_slice())
438            .unwrap()
439    }
440}
441impl From<FrankenMlsMessage> for MlsMessageIn {
442    fn from(fln: FrankenMlsMessage) -> Self {
443        MlsMessageIn::tls_deserialize(&mut fln.tls_serialize_detached().unwrap().as_slice())
444            .unwrap()
445    }
446}
447
448impl From<FrankenMlsMessage> for MlsMessageOut {
449    fn from(fln: FrankenMlsMessage) -> Self {
450        MlsMessageIn::tls_deserialize(&mut fln.tls_serialize_detached().unwrap().as_slice())
451            .unwrap()
452            .into()
453    }
454}
455
456impl From<PublicMessage> for FrankenPublicMessage {
457    fn from(ln: PublicMessage) -> Self {
458        FrankenPublicMessage::tls_deserialize(&mut ln.tls_serialize_detached().unwrap().as_slice())
459            .unwrap()
460    }
461}
462
463impl From<FrankenPublicMessage> for PublicMessage {
464    fn from(fln: FrankenPublicMessage) -> Self {
465        PublicMessageIn::tls_deserialize(&mut fln.tls_serialize_detached().unwrap().as_slice())
466            .unwrap()
467            .into()
468    }
469}
470
471impl From<PrivateMessage> for FrankenPrivateMessage {
472    fn from(ln: PrivateMessage) -> Self {
473        FrankenPrivateMessage::tls_deserialize(&mut ln.tls_serialize_detached().unwrap().as_slice())
474            .unwrap()
475    }
476}
477
478impl From<FrankenPrivateMessage> for PrivateMessage {
479    fn from(fln: FrankenPrivateMessage) -> Self {
480        PrivateMessageIn::tls_deserialize(&mut fln.tls_serialize_detached().unwrap().as_slice())
481            .unwrap()
482            .into()
483    }
484}
485
486impl From<Welcome> for FrankenWelcome {
487    fn from(ln: Welcome) -> Self {
488        FrankenWelcome::tls_deserialize(&mut ln.tls_serialize_detached().unwrap().as_slice())
489            .unwrap()
490    }
491}
492
493impl From<FrankenWelcome> for Welcome {
494    fn from(fln: FrankenWelcome) -> Self {
495        Welcome::tls_deserialize(&mut fln.tls_serialize_detached().unwrap().as_slice()).unwrap()
496    }
497}
498
499impl From<VLBytes> for JoinerSecret {
500    fn from(fln: VLBytes) -> Self {
501        JoinerSecret::tls_deserialize(&mut fln.tls_serialize_detached().unwrap().as_slice())
502            .unwrap()
503    }
504}
505
506impl From<FrankenGroupSecrets> for GroupSecrets {
507    fn from(ln: FrankenGroupSecrets) -> Self {
508        GroupSecrets::tls_deserialize(&mut ln.tls_serialize_detached().unwrap().as_slice()).unwrap()
509    }
510}
511
512impl From<FrankenFramedContentBody> for FramedContentBodyIn {
513    fn from(value: FrankenFramedContentBody) -> Self {
514        FramedContentBodyIn::tls_deserialize(
515            &mut value.tls_serialize_detached().unwrap().as_slice(),
516        )
517        .unwrap()
518    }
519}
520
521impl From<FrankenFramedContentBody> for FramedContentBody {
522    fn from(value: FrankenFramedContentBody) -> Self {
523        FramedContentBodyIn::from(value).into()
524    }
525}
526
527impl From<Sender> for FrankenSender {
528    fn from(value: Sender) -> Self {
529        match value {
530            Sender::Member(i) => FrankenSender::Member(i.u32()),
531            // this cast is safe, because the index method casts it from u32 to usize for some
532            // reason, so it's known to fit u32
533            Sender::External(i) => FrankenSender::External(i.index() as u32),
534            Sender::NewMemberProposal => FrankenSender::NewMemberProposal,
535            Sender::NewMemberCommit => FrankenSender::NewMemberCommit,
536        }
537    }
538}
539
540impl From<FrankenSender> for Sender {
541    fn from(value: FrankenSender) -> Self {
542        match value {
543            FrankenSender::Member(i) => Sender::Member(LeafNodeIndex::new(i)),
544            FrankenSender::External(i) => Sender::External(SenderExtensionIndex::new(i)),
545            FrankenSender::NewMemberProposal => Sender::NewMemberProposal,
546            FrankenSender::NewMemberCommit => Sender::NewMemberCommit,
547        }
548    }
549}