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 = "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/// The part of targeted messages that is authenticated with a MAC (used as AAD
286/// for the HPKE operation).
287///
288/// ```text
289/// struct {
290///   opaque group_id<V>;
291///   uint64 epoch;
292///   uint32 recipient_leaf_index;
293///   opaque authenticated_data<V>;
294///   uint32 sender_leaf_index;
295///   opaque kem_output<V>;
296/// } TargetedMessageTBM;
297/// ```
298#[derive(TlsSerialize, TlsSize)]
299struct TargetedMessageTBM<'a> {
300    group_id: &'a GroupId,
301    epoch: GroupEpoch,
302    recipient_leaf_index: u32,
303    authenticated_data: VLByteSlice<'a>,
304    sender_leaf_index: u32,
305    kem_output: VLByteSlice<'a>,
306}
307
308/// PSK ID for the targeted message HPKE PSK mode.
309///
310/// ```text
311/// struct {
312///   opaque group_id<V>;
313///   uint64 epoch;
314///   opaque label<V> = "MLS 1.0 targeted message psk";
315/// } PSKId;
316/// ```
317#[derive(TlsSerialize, TlsSize)]
318struct TargetedMessagePskId<'a> {
319    group_id: &'a GroupId,
320    epoch: GroupEpoch,
321    label: VLByteSlice<'a>,
322}
323
324impl<'a> TargetedMessagePskId<'a> {
325    fn new(group_id: &'a GroupId, epoch: GroupEpoch) -> Self {
326        Self {
327            group_id,
328            epoch,
329            label: VLByteSlice(PSK_LABEL.as_bytes()),
330        }
331    }
332}
333
334/// Group-level context needed for targeted message operations. The group
335/// state is bound through the exporter-derived PSK, so no serialized
336/// GroupContext is needed.
337pub(crate) struct TargetedMessageGroupContext<'a> {
338    pub ciphersuite: Ciphersuite,
339    pub group_id: &'a GroupId,
340    pub epoch: GroupEpoch,
341    pub exporter_secret: &'a crate::schedule::ExporterSecret,
342}
343
344/// Verified targeted message content, returned after successful processing.
345#[derive(Debug, Clone, PartialEq)]
346pub struct ProcessedTargetedMessage {
347    sender_leaf_index: LeafNodeIndex,
348    application_data: Vec<u8>,
349    authenticated_data: Vec<u8>,
350}
351
352impl ProcessedTargetedMessage {
353    /// Returns the sender's leaf index.
354    pub fn sender_leaf_index(&self) -> LeafNodeIndex {
355        self.sender_leaf_index
356    }
357
358    /// Returns the application data payload.
359    pub fn application_data(&self) -> &[u8] {
360        &self.application_data
361    }
362
363    /// Returns the authenticated data.
364    pub fn authenticated_data(&self) -> &[u8] {
365        &self.authenticated_data
366    }
367
368    /// Returns both the application data and authenticated data.
369    pub fn into_data(self) -> (Vec<u8>, Vec<u8>) {
370        (self.application_data, self.authenticated_data)
371    }
372}
373
374/// Derive the targeted message PSK from the MLS exporter.
375fn derive_targeted_message_psk(
376    crypto: &impl OpenMlsCrypto,
377    ciphersuite: Ciphersuite,
378    exporter_secret: &crate::schedule::ExporterSecret,
379) -> Result<Vec<u8>, LibraryError> {
380    exporter_secret
381        .derive_exported_secret(
382            ciphersuite,
383            crypto,
384            TARGETED_MESSAGE_EXPORTER_LABEL,
385            PSK_SUBLABEL.as_bytes(),
386            ciphersuite.hash_length(),
387        )
388        .map_err(LibraryError::unexpected_crypto_error)
389}
390
391/// Derive the sender auth data secret from the MLS exporter.
392fn derive_sender_auth_data_secret(
393    crypto: &impl OpenMlsCrypto,
394    ciphersuite: Ciphersuite,
395    exporter_secret: &crate::schedule::ExporterSecret,
396) -> Result<Secret, LibraryError> {
397    let secret_bytes = exporter_secret
398        .derive_exported_secret(
399            ciphersuite,
400            crypto,
401            TARGETED_MESSAGE_EXPORTER_LABEL,
402            SENDER_AUTH_DATA_SECRET_SUBLABEL.as_bytes(),
403            ciphersuite.hash_length(),
404        )
405        .map_err(LibraryError::unexpected_crypto_error)?;
406    Ok(Secret::from_slice(&secret_bytes))
407}
408
409/// Derive sender auth data key and nonce from the ciphertext sample.
410fn derive_sender_auth_data_key_nonce(
411    crypto: &impl OpenMlsCrypto,
412    ciphersuite: Ciphersuite,
413    sender_auth_data_secret: &Secret,
414    ciphertext: &[u8],
415) -> Result<(AeadKey, AeadNonce), LibraryError> {
416    let sample_len = ciphersuite.hash_length().min(ciphertext.len());
417    let ciphertext_sample = &ciphertext[..sample_len];
418
419    let key_secret = sender_auth_data_secret
420        .kdf_expand_label(
421            crypto,
422            ciphersuite,
423            "key",
424            ciphertext_sample,
425            ciphersuite.aead_key_length(),
426        )
427        .map_err(LibraryError::unexpected_crypto_error)?;
428
429    let nonce_secret = sender_auth_data_secret
430        .kdf_expand_label(
431            crypto,
432            ciphersuite,
433            "nonce",
434            ciphertext_sample,
435            ciphersuite.aead_nonce_length(),
436        )
437        .map_err(LibraryError::unexpected_crypto_error)?;
438
439    Ok((
440        AeadKey::from_secret(key_secret, ciphersuite),
441        AeadNonce::from_secret(nonce_secret),
442    ))
443}
444
445/// Wraps a TBS struct for use with the Signable/Verifiable traits.
446struct TargetedMessageTBSPayload {
447    serialized: Vec<u8>,
448}
449
450impl Signable for TargetedMessageTBSPayload {
451    type SignedOutput = TargetedMessageSignature;
452
453    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
454        Ok(self.serialized.clone())
455    }
456
457    fn label(&self) -> &str {
458        TARGETED_MESSAGE_TBS_LABEL
459    }
460}
461
462struct TargetedMessageSignature(pub(crate) Signature);
463
464impl SignedStruct<TargetedMessageTBSPayload> for TargetedMessageSignature {
465    fn from_payload(
466        _payload: TargetedMessageTBSPayload,
467        signature: Signature,
468        _serialized_payload: Vec<u8>,
469    ) -> Self {
470        Self(signature)
471    }
472}
473
474/// Wraps a TBS struct for verification.
475struct VerifiableTargetedMessageTBS {
476    serialized: Vec<u8>,
477    signature: Signature,
478}
479
480impl Verifiable for VerifiableTargetedMessageTBS {
481    type VerifiedStruct = VerifiedTargetedMessage;
482
483    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
484        Ok(self.serialized.clone())
485    }
486
487    fn signature(&self) -> &Signature {
488        &self.signature
489    }
490
491    fn label(&self) -> &str {
492        TARGETED_MESSAGE_TBS_LABEL
493    }
494
495    fn verify(
496        self,
497        crypto: &impl OpenMlsCrypto,
498        pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
499    ) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
500        self.verify_no_out(crypto, pk)?;
501        Ok(VerifiedTargetedMessage)
502    }
503}
504
505struct VerifiedTargetedMessage;
506impl VerifiedStruct for VerifiedTargetedMessage {}
507
508/// Create a targeted message.
509#[allow(clippy::too_many_arguments)]
510pub(crate) fn create_targeted_message(
511    crypto: &impl OpenMlsCrypto,
512    signer: &impl Signer,
513    ctx: &TargetedMessageGroupContext<'_>,
514    sender_leaf_index: LeafNodeIndex,
515    recipient_leaf_index: LeafNodeIndex,
516    recipient_encryption_key: &EncryptionKey,
517    authenticated_data: &[u8],
518    application_data: &[u8],
519    padding_length: usize,
520) -> Result<TargetedMessage, CreateTargetedMessageError> {
521    let psk = derive_targeted_message_psk(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
522    let sender_auth_data_secret =
523        derive_sender_auth_data_secret(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
524
525    let psk_id = TargetedMessagePskId::new(ctx.group_id, ctx.epoch);
526    let psk_id_bytes = psk_id
527        .tls_serialize_detached()
528        .map_err(LibraryError::missing_bound_check)?;
529
530    let content = TargetedMessageContent::new(application_data, padding_length);
531    let content_bytes = content
532        .serialize_detached()
533        .map_err(LibraryError::missing_bound_check)?;
534
535    let hpke_ct = recipient_encryption_key.encrypt_with_label_psk_resolved_aad(
536        crate::ciphersuite::hpke::PskEncryptParams {
537            label: TARGETED_MESSAGE_DATA_LABEL,
538            // The group state is bound through the PSK, so the context stays
539            // empty.
540            context: &[],
541            psk: &psk,
542            psk_id: &psk_id_bytes,
543            ciphersuite: ctx.ciphersuite,
544        },
545        &content_bytes,
546        crypto,
547        |kem_output| {
548            let tbm = TargetedMessageTBM {
549                group_id: ctx.group_id,
550                epoch: ctx.epoch,
551                recipient_leaf_index: recipient_leaf_index.u32(),
552                authenticated_data: VLByteSlice(authenticated_data),
553                sender_leaf_index: sender_leaf_index.u32(),
554                kem_output: VLByteSlice(kem_output),
555            };
556            tbm.tls_serialize_detached()
557                .map_err(LibraryError::missing_bound_check)
558        },
559    )?;
560
561    // The signature covers the ciphertext through its hash, so it can only be
562    // computed after the HPKE encryption.
563    let ciphertext_hash = crypto
564        .hash(
565            ctx.ciphersuite.hash_algorithm(),
566            hpke_ct.ciphertext.as_slice(),
567        )
568        .map_err(LibraryError::unexpected_crypto_error)?;
569
570    let tbs = TargetedMessageTBS {
571        version: ProtocolVersion::default(),
572        wire_format: WireFormat::TargetedMessage,
573        group_id: ctx.group_id,
574        epoch: ctx.epoch,
575        recipient_leaf_index: recipient_leaf_index.u32(),
576        authenticated_data: VLByteSlice(authenticated_data),
577        sender_leaf_index: sender_leaf_index.u32(),
578        kem_output: VLByteSlice(hpke_ct.kem_output.as_slice()),
579        ciphertext_hash: VLByteSlice(&ciphertext_hash),
580    };
581    let tbs_bytes = tbs
582        .tls_serialize_detached()
583        .map_err(LibraryError::missing_bound_check)?;
584
585    let tbs_payload = TargetedMessageTBSPayload {
586        serialized: tbs_bytes,
587    };
588    let signature = tbs_payload.sign(signer).map_err(|e| {
589        log::error!("Signing targeted message failed: {e:?}");
590        LibraryError::custom("Signing targeted message failed")
591    })?;
592
593    let sender_auth_data = TargetedMessageSenderAuthData {
594        sender_leaf_index: sender_leaf_index.u32(),
595        signature: signature.0,
596        kem_output: hpke_ct.kem_output.as_slice().to_vec().into(),
597    };
598    let sender_auth_data_bytes = sender_auth_data
599        .tls_serialize_detached()
600        .map_err(LibraryError::missing_bound_check)?;
601
602    // Encrypt sender auth data
603    let (key, nonce) = derive_sender_auth_data_key_nonce(
604        crypto,
605        ctx.ciphersuite,
606        &sender_auth_data_secret,
607        hpke_ct.ciphertext.as_slice(),
608    )?;
609
610    let sender_auth_aad = SenderAuthDataAAD {
611        group_id: ctx.group_id,
612        epoch: ctx.epoch,
613        recipient_leaf_index: recipient_leaf_index.u32(),
614    };
615    let sender_auth_aad_bytes = sender_auth_aad
616        .tls_serialize_detached()
617        .map_err(LibraryError::missing_bound_check)?;
618
619    let encrypted_sender_auth_data = key
620        .aead_seal(
621            crypto,
622            &sender_auth_data_bytes,
623            &sender_auth_aad_bytes,
624            &nonce,
625        )
626        .map_err(LibraryError::unexpected_crypto_error)?;
627
628    Ok(TargetedMessage {
629        group_id: ctx.group_id.clone(),
630        epoch: ctx.epoch,
631        recipient_leaf_index: recipient_leaf_index.u32(),
632        authenticated_data: authenticated_data.to_vec().into(),
633        encrypted_sender_auth_data: encrypted_sender_auth_data.into(),
634        ciphertext: hpke_ct.ciphertext,
635    })
636}
637
638/// Process (decrypt and verify) a targeted message.
639pub(crate) fn process_targeted_message<StorageError>(
640    crypto: &impl OpenMlsCrypto,
641    ctx: &TargetedMessageGroupContext<'_>,
642    own_leaf_index: LeafNodeIndex,
643    own_encryption_private_key: &EncryptionPrivateKey,
644    message: &TargetedMessageIn,
645    leaves: &[Option<&LeafNode>],
646) -> Result<ProcessedTargetedMessage, ProcessTargetedMessageError<StorageError>> {
647    // Validate group_id
648    if &message.group_id != ctx.group_id {
649        return Err(ProcessTargetedMessageError::GroupIdMismatch);
650    }
651
652    // Validate epoch
653    if message.epoch != ctx.epoch {
654        return Err(ProcessTargetedMessageError::EpochMismatch);
655    }
656
657    // Validate recipient
658    if message.recipient_leaf_index != own_leaf_index.u32() {
659        return Err(ProcessTargetedMessageError::NotIntendedRecipient);
660    }
661
662    let sender_auth_data_secret =
663        derive_sender_auth_data_secret(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
664
665    // Derive key/nonce for sender auth data decryption
666    let (key, nonce) = derive_sender_auth_data_key_nonce(
667        crypto,
668        ctx.ciphersuite,
669        &sender_auth_data_secret,
670        message.ciphertext.as_slice(),
671    )?;
672
673    let sender_auth_aad = SenderAuthDataAAD {
674        group_id: ctx.group_id,
675        epoch: ctx.epoch,
676        recipient_leaf_index: own_leaf_index.u32(),
677    };
678    let sender_auth_aad_bytes = sender_auth_aad
679        .tls_serialize_detached()
680        .map_err(LibraryError::missing_bound_check)?;
681
682    // Decrypt sender auth data
683    let sender_auth_data_bytes = key
684        .aead_open(
685            crypto,
686            message.encrypted_sender_auth_data.as_slice(),
687            &sender_auth_aad_bytes,
688            &nonce,
689        )
690        .map_err(|e| {
691            log::error!("Targeted message sender auth data decryption failed: {e:?}");
692            ProcessTargetedMessageError::SenderAuthDataDecryptionFailed
693        })?;
694
695    let sender_auth_data =
696        TargetedMessageSenderAuthData::tls_deserialize_exact_bytes(&sender_auth_data_bytes)
697            .map_err(|e| {
698                log::error!("Targeted message sender auth data is malformed: {e:?}");
699                ProcessTargetedMessageError::MalformedSenderAuthData
700            })?;
701
702    let sender_leaf_index = LeafNodeIndex::new(sender_auth_data.sender_leaf_index);
703
704    let sender_leaf = leaves
705        .get(sender_leaf_index.usize())
706        .and_then(|opt| opt.as_ref())
707        .ok_or(ProcessTargetedMessageError::SenderNotFound)?;
708    let sender_signature_key = OpenMlsSignaturePublicKey::from_signature_key(
709        sender_leaf.signature_key().clone(),
710        ctx.ciphersuite.signature_algorithm(),
711    );
712
713    // Verify signature over TBS. The ciphertext hash is computed from the
714    // wire-format ciphertext, so verification does not require decryption.
715    let ciphertext_hash = crypto
716        .hash(
717            ctx.ciphersuite.hash_algorithm(),
718            message.ciphertext.as_slice(),
719        )
720        .map_err(LibraryError::unexpected_crypto_error)?;
721
722    let tbs = TargetedMessageTBS {
723        version: ProtocolVersion::default(),
724        wire_format: WireFormat::TargetedMessage,
725        group_id: ctx.group_id,
726        epoch: ctx.epoch,
727        recipient_leaf_index: own_leaf_index.u32(),
728        authenticated_data: VLByteSlice(message.authenticated_data.as_slice()),
729        sender_leaf_index: sender_auth_data.sender_leaf_index,
730        kem_output: VLByteSlice(sender_auth_data.kem_output.as_slice()),
731        ciphertext_hash: VLByteSlice(&ciphertext_hash),
732    };
733    let tbs_bytes = tbs
734        .tls_serialize_detached()
735        .map_err(LibraryError::missing_bound_check)?;
736
737    let verifiable = VerifiableTargetedMessageTBS {
738        serialized: tbs_bytes,
739        signature: sender_auth_data.signature.clone(),
740    };
741
742    verifiable
743        .verify(crypto, &sender_signature_key)
744        .map_err(|e| {
745            log::error!("Targeted message signature verification failed: {e:?}");
746            ProcessTargetedMessageError::SignatureVerificationFailed
747        })?;
748
749    // Decrypt the content via HPKE PSK open
750    let psk = derive_targeted_message_psk(crypto, ctx.ciphersuite, ctx.exporter_secret)?;
751
752    let psk_id = TargetedMessagePskId::new(ctx.group_id, ctx.epoch);
753    let psk_id_bytes = psk_id
754        .tls_serialize_detached()
755        .map_err(LibraryError::missing_bound_check)?;
756
757    let tbm = TargetedMessageTBM {
758        group_id: ctx.group_id,
759        epoch: ctx.epoch,
760        recipient_leaf_index: own_leaf_index.u32(),
761        authenticated_data: VLByteSlice(message.authenticated_data.as_slice()),
762        sender_leaf_index: sender_auth_data.sender_leaf_index,
763        kem_output: VLByteSlice(sender_auth_data.kem_output.as_slice()),
764    };
765    let tbm_bytes = tbm
766        .tls_serialize_detached()
767        .map_err(LibraryError::missing_bound_check)?;
768
769    let hpke_ciphertext = HpkeCiphertext {
770        kem_output: sender_auth_data.kem_output.as_slice().to_vec().into(),
771        ciphertext: message.ciphertext.as_slice().to_vec().into(),
772    };
773
774    let content_bytes = own_encryption_private_key
775        .decrypt_with_label_psk_aad(
776            crate::ciphersuite::hpke::PskEncryptParams {
777                label: TARGETED_MESSAGE_DATA_LABEL,
778                // The group state is bound through the PSK, so the context
779                // stays empty.
780                context: &[],
781                psk: &psk,
782                psk_id: &psk_id_bytes,
783                ciphersuite: ctx.ciphersuite,
784            },
785            &tbm_bytes,
786            &hpke_ciphertext,
787            crypto,
788        )
789        .map_err(|e| {
790            log::error!("Targeted message content decryption failed: {e:?}");
791            ProcessTargetedMessageError::ContentDecryptionFailed
792        })?;
793
794    let content = TargetedMessageContent::deserialize_detached(&content_bytes).map_err(|e| {
795        log::error!("Targeted message content is malformed: {e:?}");
796        ProcessTargetedMessageError::MalformedContent
797    })?;
798
799    Ok(ProcessedTargetedMessage {
800        sender_leaf_index,
801        application_data: content.application_data().to_vec(),
802        authenticated_data: message.authenticated_data.as_slice().to_vec(),
803    })
804}