Skip to main content

openmls/targeted_messages/
mod.rs

1//! # Targeted Messages (draft-ietf-mls-targeted-messages)
2//!
3//! This module implements the MLS targeted messages extension, which allows
4//! a group member to send an HPKE-encrypted message to a specific member
5//! of the group. The message is authenticated using the group's PSK and
6//! the sender's signature key.
7
8mod errors;
9
10#[cfg(test)]
11pub mod kat;
12
13#[cfg(test)]
14mod tests;
15
16pub use errors::*;
17
18use openmls_traits::{
19    crypto::OpenMlsCrypto,
20    signatures::Signer,
21    types::{Ciphersuite, HpkeCiphertext},
22};
23use serde::{Deserialize, Serialize};
24use tls_codec::{
25    DeserializeBytes, Serialize as TlsSerializeTrait, Size, TlsDeserialize, TlsDeserializeBytes,
26    TlsSerialize, TlsSize, VLByteSlice, VLBytes,
27};
28
29use crate::{
30    binary_tree::array_representation::LeafNodeIndex,
31    ciphersuite::{
32        signable::{Signable, SignedStruct, Verifiable, VerifiedStruct},
33        AeadKey, AeadNonce, OpenMlsSignaturePublicKey, Secret, Signature,
34    },
35    error::LibraryError,
36    framing::WireFormat,
37    group::{GroupEpoch, GroupId},
38    treesync::node::{
39        encryption_keys::{EncryptionKey, EncryptionPrivateKey},
40        leaf_node::LeafNode,
41    },
42    versions::ProtocolVersion,
43};
44
45const TARGETED_MESSAGE_EXPORTER_LABEL: &str = "targeted message";
46const PSK_SUBLABEL: &str = "psk";
47const SENDER_AUTH_DATA_SECRET_SUBLABEL: &str = "sender auth data secret";
48const TARGETED_MESSAGE_TBS_LABEL: &str = "TargetedMessageTBS";
49const TARGETED_MESSAGE_DATA_LABEL: &str = "MLS 1.0 TargetedMessageData";
50const PSK_LABEL: &str = "MLS 1.0 targeted message psk";
51
52/// A targeted message as defined in draft-ietf-mls-targeted-messages.
53///
54/// ```text
55/// struct {
56///   opaque group_id<V>;
57///   uint64 epoch;
58///   uint32 recipient_leaf_index;
59///   opaque authenticated_data<V>;
60///   opaque encrypted_sender_auth_data<V>;
61///   opaque ciphertext<V>;
62/// } TargetedMessage;
63/// ```
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TlsSerialize, TlsSize)]
65pub struct TargetedMessage {
66    pub(crate) group_id: GroupId,
67    pub(crate) epoch: GroupEpoch,
68    pub(crate) recipient_leaf_index: u32,
69    pub(crate) authenticated_data: VLBytes,
70    pub(crate) encrypted_sender_auth_data: VLBytes,
71    pub(crate) ciphertext: VLBytes,
72}
73
74impl TargetedMessage {
75    /// Returns the group ID.
76    pub fn group_id(&self) -> &GroupId {
77        &self.group_id
78    }
79
80    /// Returns the epoch.
81    pub fn epoch(&self) -> GroupEpoch {
82        self.epoch
83    }
84
85    /// Returns the recipient leaf index.
86    pub fn recipient_leaf_index(&self) -> u32 {
87        self.recipient_leaf_index
88    }
89
90    /// Returns the authenticated data.
91    pub fn authenticated_data(&self) -> &[u8] {
92        self.authenticated_data.as_slice()
93    }
94}
95
96/// A received targeted message, used as input for processing.
97#[derive(Debug, Clone, PartialEq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
98pub struct TargetedMessageIn {
99    pub(crate) group_id: GroupId,
100    pub(crate) epoch: GroupEpoch,
101    pub(crate) recipient_leaf_index: u32,
102    pub(crate) authenticated_data: VLBytes,
103    pub(crate) encrypted_sender_auth_data: VLBytes,
104    pub(crate) ciphertext: VLBytes,
105}
106
107impl TargetedMessageIn {
108    /// Returns the group ID.
109    pub fn group_id(&self) -> &GroupId {
110        &self.group_id
111    }
112
113    /// Returns the epoch.
114    pub fn epoch(&self) -> GroupEpoch {
115        self.epoch
116    }
117
118    /// Returns the recipient leaf index.
119    pub fn recipient_leaf_index(&self) -> u32 {
120        self.recipient_leaf_index
121    }
122
123    /// Returns the authenticated data.
124    pub fn authenticated_data(&self) -> &[u8] {
125        self.authenticated_data.as_slice()
126    }
127}
128
129#[cfg(any(feature = "test-utils", test))]
130impl From<TargetedMessageIn> for TargetedMessage {
131    fn from(msg: TargetedMessageIn) -> Self {
132        Self {
133            group_id: msg.group_id,
134            epoch: msg.epoch,
135            recipient_leaf_index: msg.recipient_leaf_index,
136            authenticated_data: msg.authenticated_data,
137            encrypted_sender_auth_data: msg.encrypted_sender_auth_data,
138            ciphertext: msg.ciphertext,
139        }
140    }
141}
142
143impl From<TargetedMessage> for TargetedMessageIn {
144    fn from(msg: TargetedMessage) -> Self {
145        Self {
146            group_id: msg.group_id,
147            epoch: msg.epoch,
148            recipient_leaf_index: msg.recipient_leaf_index,
149            authenticated_data: msg.authenticated_data,
150            encrypted_sender_auth_data: msg.encrypted_sender_auth_data,
151            ciphertext: msg.ciphertext,
152        }
153    }
154}
155
156/// The plaintext content of a targeted message.
157///
158/// ```text
159/// struct {
160///   opaque application_data<V>;
161///   opaque padding[length_of_padding];
162/// } TargetedMessageContent;
163/// ```
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub(crate) struct TargetedMessageContent {
166    application_data: VLBytes,
167    padding_length: usize,
168}
169
170impl TargetedMessageContent {
171    fn new(data: &[u8], padding_length: usize) -> Self {
172        Self {
173            application_data: data.into(),
174            padding_length,
175        }
176    }
177
178    pub(crate) fn application_data(&self) -> &[u8] {
179        self.application_data.as_slice()
180    }
181
182    /// Serialize to bytes: VLBytes application_data followed by raw zero
183    /// padding (no length prefix on the padding).
184    fn serialize_detached(&self) -> Result<Vec<u8>, tls_codec::Error> {
185        use std::io::Write;
186        let app_data_len = self.application_data.tls_serialized_len();
187        // Guard against overflow and against exceeding Rust's allocation limit
188        // of isize::MAX bytes, since padding_length is caller-controlled.
189        let total_len = app_data_len
190            .checked_add(self.padding_length)
191            .filter(|&len| len <= isize::MAX as usize)
192            .ok_or_else(|| {
193                tls_codec::Error::EncodingError(
194                    "Targeted message content exceeds the maximum size.".into(),
195                )
196            })?;
197        let mut buffer = Vec::with_capacity(total_len);
198        self.application_data.tls_serialize(&mut buffer)?;
199        buffer
200            .write_all(&vec![0u8; self.padding_length])
201            .map_err(|e| {
202                tls_codec::Error::EncodingError(format!("Failed to write padding: {e}"))
203            })?;
204        Ok(buffer)
205    }
206
207    /// Deserialize: read VLBytes application_data, treat remaining bytes as
208    /// raw padding, and validate all padding bytes are zero.
209    fn deserialize_detached(bytes: &[u8]) -> Result<Self, tls_codec::Error> {
210        let (application_data, rest) = VLBytes::tls_deserialize_bytes(bytes)?;
211        if !rest.iter().all(|&b| b == 0x00) {
212            return Err(tls_codec::Error::DecodingError(
213                "Non-zero padding in TargetedMessageContent".into(),
214            ));
215        }
216        Ok(Self {
217            application_data,
218            padding_length: rest.len(),
219        })
220    }
221}
222
223/// Sender authentication data, encrypted within the targeted message.
224///
225/// ```text
226/// struct {
227///   uint32 sender_leaf_index;
228///   opaque signature<V>;
229///   opaque kem_output<V>;
230/// } TargetedMessageSenderAuthData;
231/// ```
232#[derive(Debug, Clone, PartialEq, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
233struct TargetedMessageSenderAuthData {
234    sender_leaf_index: u32,
235    signature: Signature,
236    kem_output: VLBytes,
237}
238
239/// AAD for encrypting sender authentication data.
240///
241/// ```text
242/// struct {
243///   opaque group_id<V>;
244///   uint64 epoch;
245///   uint32 recipient_leaf_index;
246/// } SenderAuthDataAAD;
247/// ```
248#[derive(TlsSerialize, TlsSize)]
249struct SenderAuthDataAAD<'a> {
250    group_id: &'a GroupId,
251    epoch: GroupEpoch,
252    recipient_leaf_index: u32,
253}
254
255/// The part of targeted messages that is authenticated with a signature. The
256/// `ciphertext_hash` field binds the signature to the encrypted message
257/// content.
258///
259/// ```text
260/// struct {
261///   ProtocolVersion version = mls10;
262///   WireFormat wire_format = mls_targeted_message;
263///   opaque group_id<V>;
264///   uint64 epoch;
265///   uint32 recipient_leaf_index;
266///   opaque authenticated_data<V>;
267///   uint32 sender_leaf_index;
268///   opaque kem_output<V>;
269///   opaque ciphertext_hash<V>;
270/// } TargetedMessageTBS;
271/// ```
272#[derive(TlsSerialize, TlsSize)]
273struct TargetedMessageTBS<'a> {
274    version: ProtocolVersion,
275    wire_format: WireFormat,
276    group_id: &'a GroupId,
277    epoch: GroupEpoch,
278    recipient_leaf_index: u32,
279    authenticated_data: VLByteSlice<'a>,
280    sender_leaf_index: u32,
281    kem_output: VLByteSlice<'a>,
282    ciphertext_hash: VLByteSlice<'a>,
283}
284
285/// HPKE context data binding the encryption to the recipient and sender.
286///
287/// ```text
288/// struct {
289///   uint32 recipient_leaf_index;
290///   uint32 sender_leaf_index;
291/// } TargetedMessageContextData;
292/// ```
293#[derive(TlsSerialize, TlsSize)]
294struct TargetedMessageContextData {
295    recipient_leaf_index: u32,
296    sender_leaf_index: u32,
297}
298
299/// HPKE info for the targeted message encryption.
300///
301/// ```text
302/// struct {
303///   opaque label<V> = "MLS 1.0 TargetedMessageData";
304///   TargetedMessageContextData context;
305/// } TargetedMessageContext;
306/// ```
307#[derive(TlsSerialize, TlsSize)]
308struct TargetedMessageContext<'a> {
309    label: VLByteSlice<'a>,
310    context: TargetedMessageContextData,
311}
312
313/// Serialize the HPKE info for the targeted message content encryption.
314fn targeted_message_hpke_info(
315    recipient_leaf_index: u32,
316    sender_leaf_index: u32,
317) -> Result<Vec<u8>, LibraryError> {
318    TargetedMessageContext {
319        label: VLByteSlice(TARGETED_MESSAGE_DATA_LABEL.as_bytes()),
320        context: TargetedMessageContextData {
321            recipient_leaf_index,
322            sender_leaf_index,
323        },
324    }
325    .tls_serialize_detached()
326    .map_err(LibraryError::missing_bound_check)
327}
328
329/// PSK ID for the targeted message HPKE PSK mode.
330///
331/// ```text
332/// struct {
333///   opaque group_id<V>;
334///   uint64 epoch;
335///   opaque label<V> = "MLS 1.0 targeted message psk";
336/// } PSKId;
337/// ```
338#[derive(TlsSerialize, TlsSize)]
339struct TargetedMessagePskId<'a> {
340    group_id: &'a GroupId,
341    epoch: GroupEpoch,
342    label: VLByteSlice<'a>,
343}
344
345impl<'a> TargetedMessagePskId<'a> {
346    fn new(group_id: &'a GroupId, epoch: GroupEpoch) -> Self {
347        Self {
348            group_id,
349            epoch,
350            label: VLByteSlice(PSK_LABEL.as_bytes()),
351        }
352    }
353}
354
355/// Group-level context needed for targeted message operations. The group
356/// state is bound through the exporter-derived PSK, so no serialized
357/// GroupContext is needed.
358pub(crate) struct TargetedMessageGroupContext<'a> {
359    pub ciphersuite: Ciphersuite,
360    pub group_id: &'a GroupId,
361    pub epoch: GroupEpoch,
362    pub exporter_secret: &'a crate::schedule::ExporterSecret,
363}
364
365/// Verified targeted message content, returned after successful processing.
366#[derive(Debug, Clone, PartialEq)]
367pub struct ProcessedTargetedMessage {
368    sender_leaf_index: LeafNodeIndex,
369    application_data: Vec<u8>,
370    authenticated_data: Vec<u8>,
371}
372
373impl ProcessedTargetedMessage {
374    /// Returns the sender's leaf index.
375    pub fn sender_leaf_index(&self) -> LeafNodeIndex {
376        self.sender_leaf_index
377    }
378
379    /// Returns the application data payload.
380    pub fn application_data(&self) -> &[u8] {
381        &self.application_data
382    }
383
384    /// Returns the authenticated data.
385    pub fn authenticated_data(&self) -> &[u8] {
386        &self.authenticated_data
387    }
388
389    /// Returns both the application data and authenticated data.
390    pub fn into_data(self) -> (Vec<u8>, Vec<u8>) {
391        (self.application_data, self.authenticated_data)
392    }
393}
394
395/// Derive the targeted message PSK from the MLS exporter.
396fn derive_targeted_message_psk(
397    crypto: &impl OpenMlsCrypto,
398    ciphersuite: Ciphersuite,
399    exporter_secret: &crate::schedule::ExporterSecret,
400) -> Result<Secret, LibraryError> {
401    exporter_secret
402        .derive_exported_secret(
403            ciphersuite,
404            crypto,
405            TARGETED_MESSAGE_EXPORTER_LABEL,
406            PSK_SUBLABEL.as_bytes(),
407            ciphersuite.hash_length(),
408        )
409        .map_err(LibraryError::unexpected_crypto_error)
410}
411
412/// Derive the sender auth data secret from the MLS exporter.
413fn derive_sender_auth_data_secret(
414    crypto: &impl OpenMlsCrypto,
415    ciphersuite: Ciphersuite,
416    exporter_secret: &crate::schedule::ExporterSecret,
417) -> Result<Secret, LibraryError> {
418    exporter_secret
419        .derive_exported_secret(
420            ciphersuite,
421            crypto,
422            TARGETED_MESSAGE_EXPORTER_LABEL,
423            SENDER_AUTH_DATA_SECRET_SUBLABEL.as_bytes(),
424            ciphersuite.hash_length(),
425        )
426        .map_err(LibraryError::unexpected_crypto_error)
427}
428
429/// Derive sender auth data key and nonce from the ciphertext sample.
430fn derive_sender_auth_data_key_nonce(
431    crypto: &impl OpenMlsCrypto,
432    ciphersuite: Ciphersuite,
433    sender_auth_data_secret: &Secret,
434    ciphertext: &[u8],
435) -> Result<(AeadKey, AeadNonce), LibraryError> {
436    let sample_len = ciphersuite.hash_length().min(ciphertext.len());
437    let ciphertext_sample = &ciphertext[..sample_len];
438
439    let key_secret = sender_auth_data_secret
440        .kdf_expand_label(
441            crypto,
442            ciphersuite,
443            "key",
444            ciphertext_sample,
445            ciphersuite.aead_key_length(),
446        )
447        .map_err(LibraryError::unexpected_crypto_error)?;
448
449    let nonce_secret = sender_auth_data_secret
450        .kdf_expand_label(
451            crypto,
452            ciphersuite,
453            "nonce",
454            ciphertext_sample,
455            ciphersuite.aead_nonce_length(),
456        )
457        .map_err(LibraryError::unexpected_crypto_error)?;
458
459    Ok((
460        AeadKey::from_secret(key_secret, ciphersuite),
461        AeadNonce::from_secret(nonce_secret),
462    ))
463}
464
465/// Wraps a TBS struct for use with the Signable/Verifiable traits.
466struct TargetedMessageTBSPayload {
467    serialized: Vec<u8>,
468}
469
470impl Signable for TargetedMessageTBSPayload {
471    type SignedOutput = TargetedMessageSignature;
472
473    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
474        Ok(self.serialized.clone())
475    }
476
477    fn label(&self) -> &str {
478        TARGETED_MESSAGE_TBS_LABEL
479    }
480}
481
482struct TargetedMessageSignature(pub(crate) Signature);
483
484impl SignedStruct<TargetedMessageTBSPayload> for TargetedMessageSignature {
485    fn from_payload(
486        _payload: TargetedMessageTBSPayload,
487        signature: Signature,
488        _serialized_payload: Vec<u8>,
489    ) -> Self {
490        Self(signature)
491    }
492}
493
494/// Wraps a TBS struct for verification.
495struct VerifiableTargetedMessageTBS {
496    serialized: Vec<u8>,
497    signature: Signature,
498}
499
500impl Verifiable for VerifiableTargetedMessageTBS {
501    type VerifiedStruct = VerifiedTargetedMessage;
502
503    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
504        Ok(self.serialized.clone())
505    }
506
507    fn signature(&self) -> &Signature {
508        &self.signature
509    }
510
511    fn label(&self) -> &str {
512        TARGETED_MESSAGE_TBS_LABEL
513    }
514
515    fn verify(
516        self,
517        crypto: &impl OpenMlsCrypto,
518        pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
519    ) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
520        self.verify_no_out(crypto, pk)?;
521        Ok(VerifiedTargetedMessage)
522    }
523}
524
525struct VerifiedTargetedMessage;
526impl VerifiedStruct for VerifiedTargetedMessage {}
527
528/// Create a targeted message.
529#[allow(clippy::too_many_arguments)]
530pub(crate) fn create_targeted_message(
531    crypto: &impl OpenMlsCrypto,
532    signer: &impl Signer,
533    ctx: &TargetedMessageGroupContext<'_>,
534    sender_leaf_index: LeafNodeIndex,
535    recipient_leaf_index: LeafNodeIndex,
536    recipient_encryption_key: &EncryptionKey,
537    authenticated_data: &[u8],
538    application_data: &[u8],
539    padding_length: usize,
540) -> Result<TargetedMessage, CreateTargetedMessageError> {
541    let psk = derive_targeted_message_psk(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
542    let sender_auth_data_secret =
543        derive_sender_auth_data_secret(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
544
545    let psk_id = TargetedMessagePskId::new(ctx.group_id, ctx.epoch);
546    let psk_id_bytes = psk_id
547        .tls_serialize_detached()
548        .map_err(LibraryError::missing_bound_check)?;
549
550    let content = TargetedMessageContent::new(application_data, padding_length);
551    let content_bytes = content
552        .serialize_detached()
553        .map_err(LibraryError::missing_bound_check)?;
554
555    let info = targeted_message_hpke_info(recipient_leaf_index.u32(), sender_leaf_index.u32())?;
556
557    let hpke_ct = recipient_encryption_key.seal_psk(
558        crate::ciphersuite::hpke::PskEncryptParams {
559            info: &info,
560            psk: psk.as_slice(),
561            psk_id: &psk_id_bytes,
562            ciphersuite: ctx.ciphersuite,
563        },
564        authenticated_data,
565        &content_bytes,
566        crypto,
567    )?;
568
569    // The signature covers the ciphertext through its hash, so it can only be
570    // computed after the HPKE encryption.
571    let ciphertext_hash = crypto
572        .hash(
573            ctx.ciphersuite.hash_algorithm(),
574            hpke_ct.ciphertext.as_slice(),
575        )
576        .map_err(LibraryError::unexpected_crypto_error)?;
577
578    let tbs = TargetedMessageTBS {
579        version: ProtocolVersion::default(),
580        wire_format: WireFormat::TargetedMessage,
581        group_id: ctx.group_id,
582        epoch: ctx.epoch,
583        recipient_leaf_index: recipient_leaf_index.u32(),
584        authenticated_data: VLByteSlice(authenticated_data),
585        sender_leaf_index: sender_leaf_index.u32(),
586        kem_output: VLByteSlice(hpke_ct.kem_output.as_slice()),
587        ciphertext_hash: VLByteSlice(&ciphertext_hash),
588    };
589    let tbs_bytes = tbs
590        .tls_serialize_detached()
591        .map_err(LibraryError::missing_bound_check)?;
592
593    let tbs_payload = TargetedMessageTBSPayload {
594        serialized: tbs_bytes,
595    };
596    let signature = tbs_payload.sign(signer).map_err(|e| {
597        log::error!("Signing targeted message failed: {e:?}");
598        LibraryError::custom("Signing targeted message failed")
599    })?;
600
601    let sender_auth_data = TargetedMessageSenderAuthData {
602        sender_leaf_index: sender_leaf_index.u32(),
603        signature: signature.0,
604        kem_output: hpke_ct.kem_output.as_slice().to_vec().into(),
605    };
606    let sender_auth_data_bytes = sender_auth_data
607        .tls_serialize_detached()
608        .map_err(LibraryError::missing_bound_check)?;
609
610    // Encrypt sender auth data
611    let (key, nonce) = derive_sender_auth_data_key_nonce(
612        crypto,
613        ctx.ciphersuite,
614        &sender_auth_data_secret,
615        hpke_ct.ciphertext.as_slice(),
616    )?;
617
618    let sender_auth_aad = SenderAuthDataAAD {
619        group_id: ctx.group_id,
620        epoch: ctx.epoch,
621        recipient_leaf_index: recipient_leaf_index.u32(),
622    };
623    let sender_auth_aad_bytes = sender_auth_aad
624        .tls_serialize_detached()
625        .map_err(LibraryError::missing_bound_check)?;
626
627    let encrypted_sender_auth_data = key
628        .aead_seal(
629            crypto,
630            &sender_auth_data_bytes,
631            &sender_auth_aad_bytes,
632            &nonce,
633        )
634        .map_err(LibraryError::unexpected_crypto_error)?;
635
636    Ok(TargetedMessage {
637        group_id: ctx.group_id.clone(),
638        epoch: ctx.epoch,
639        recipient_leaf_index: recipient_leaf_index.u32(),
640        authenticated_data: authenticated_data.to_vec().into(),
641        encrypted_sender_auth_data: encrypted_sender_auth_data.into(),
642        ciphertext: hpke_ct.ciphertext,
643    })
644}
645
646/// Process (decrypt and verify) a targeted message.
647pub(crate) fn process_targeted_message<StorageError>(
648    crypto: &impl OpenMlsCrypto,
649    ctx: &TargetedMessageGroupContext<'_>,
650    own_leaf_index: LeafNodeIndex,
651    own_encryption_private_key: &EncryptionPrivateKey,
652    message: &TargetedMessageIn,
653    leaves: &[Option<&LeafNode>],
654) -> Result<ProcessedTargetedMessage, ProcessTargetedMessageError<StorageError>> {
655    // Validate group_id
656    if &message.group_id != ctx.group_id {
657        return Err(ProcessTargetedMessageError::GroupIdMismatch);
658    }
659
660    // Validate epoch
661    if message.epoch != ctx.epoch {
662        return Err(ProcessTargetedMessageError::EpochMismatch);
663    }
664
665    // Validate recipient
666    if message.recipient_leaf_index != own_leaf_index.u32() {
667        return Err(ProcessTargetedMessageError::NotIntendedRecipient);
668    }
669
670    let sender_auth_data_secret =
671        derive_sender_auth_data_secret(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
672
673    // Derive key/nonce for sender auth data decryption
674    let (key, nonce) = derive_sender_auth_data_key_nonce(
675        crypto,
676        ctx.ciphersuite,
677        &sender_auth_data_secret,
678        message.ciphertext.as_slice(),
679    )?;
680
681    let sender_auth_aad = SenderAuthDataAAD {
682        group_id: ctx.group_id,
683        epoch: ctx.epoch,
684        recipient_leaf_index: own_leaf_index.u32(),
685    };
686    let sender_auth_aad_bytes = sender_auth_aad
687        .tls_serialize_detached()
688        .map_err(LibraryError::missing_bound_check)?;
689
690    // Decrypt sender auth data
691    let sender_auth_data_bytes = key
692        .aead_open(
693            crypto,
694            message.encrypted_sender_auth_data.as_slice(),
695            &sender_auth_aad_bytes,
696            &nonce,
697        )
698        .map_err(|e| {
699            log::error!("Targeted message sender auth data decryption failed: {e:?}");
700            ProcessTargetedMessageError::SenderAuthDataDecryptionFailed
701        })?;
702
703    let sender_auth_data =
704        TargetedMessageSenderAuthData::tls_deserialize_exact_bytes(&sender_auth_data_bytes)
705            .map_err(|e| {
706                log::error!("Targeted message sender auth data is malformed: {e:?}");
707                ProcessTargetedMessageError::MalformedSenderAuthData
708            })?;
709
710    let sender_leaf_index = LeafNodeIndex::new(sender_auth_data.sender_leaf_index);
711
712    let sender_leaf = leaves
713        .get(sender_leaf_index.usize())
714        .and_then(|opt| opt.as_ref())
715        .ok_or(ProcessTargetedMessageError::SenderNotFound)?;
716    let sender_signature_key = OpenMlsSignaturePublicKey::from_signature_key(
717        sender_leaf.signature_key().clone(),
718        ctx.ciphersuite.signature_algorithm(),
719    );
720
721    // Verify signature over TBS. The ciphertext hash is computed from the
722    // wire-format ciphertext, so verification does not require decryption.
723    let ciphertext_hash = crypto
724        .hash(
725            ctx.ciphersuite.hash_algorithm(),
726            message.ciphertext.as_slice(),
727        )
728        .map_err(LibraryError::unexpected_crypto_error)?;
729
730    let tbs = TargetedMessageTBS {
731        version: ProtocolVersion::default(),
732        wire_format: WireFormat::TargetedMessage,
733        group_id: ctx.group_id,
734        epoch: ctx.epoch,
735        recipient_leaf_index: own_leaf_index.u32(),
736        authenticated_data: VLByteSlice(message.authenticated_data.as_slice()),
737        sender_leaf_index: sender_auth_data.sender_leaf_index,
738        kem_output: VLByteSlice(sender_auth_data.kem_output.as_slice()),
739        ciphertext_hash: VLByteSlice(&ciphertext_hash),
740    };
741    let tbs_bytes = tbs
742        .tls_serialize_detached()
743        .map_err(LibraryError::missing_bound_check)?;
744
745    let verifiable = VerifiableTargetedMessageTBS {
746        serialized: tbs_bytes,
747        signature: sender_auth_data.signature.clone(),
748    };
749
750    verifiable
751        .verify(crypto, &sender_signature_key)
752        .map_err(|e| {
753            log::error!("Targeted message signature verification failed: {e:?}");
754            ProcessTargetedMessageError::SignatureVerificationFailed
755        })?;
756
757    // Decrypt the content via HPKE PSK open
758    let psk = derive_targeted_message_psk(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
759
760    let psk_id = TargetedMessagePskId::new(ctx.group_id, ctx.epoch);
761    let psk_id_bytes = psk_id
762        .tls_serialize_detached()
763        .map_err(LibraryError::missing_bound_check)?;
764
765    let info =
766        targeted_message_hpke_info(own_leaf_index.u32(), sender_auth_data.sender_leaf_index)?;
767
768    let hpke_ciphertext = HpkeCiphertext {
769        kem_output: sender_auth_data.kem_output.as_slice().to_vec().into(),
770        ciphertext: message.ciphertext.as_slice().to_vec().into(),
771    };
772
773    let content_bytes = own_encryption_private_key
774        .open_psk(
775            crate::ciphersuite::hpke::PskEncryptParams {
776                info: &info,
777                psk: psk.as_slice(),
778                psk_id: &psk_id_bytes,
779                ciphersuite: ctx.ciphersuite,
780            },
781            message.authenticated_data.as_slice(),
782            &hpke_ciphertext,
783            crypto,
784        )
785        .map_err(|e| {
786            log::error!("Targeted message content decryption failed: {e:?}");
787            ProcessTargetedMessageError::ContentDecryptionFailed
788        })?;
789
790    let content = TargetedMessageContent::deserialize_detached(&content_bytes).map_err(|e| {
791        log::error!("Targeted message content is malformed: {e:?}");
792        ProcessTargetedMessageError::MalformedContent
793    })?;
794
795    Ok(ProcessedTargetedMessage {
796        sender_leaf_index,
797        application_data: content.application_data().to_vec(),
798        authenticated_data: message.authenticated_data.as_slice().to_vec(),
799    })
800}