Skip to main content

rustrtc/
srtp.rs

1use crate::{
2    errors::{SrtpError, SrtpResult},
3    rtp::{RtpHeader, RtpPacket},
4};
5use aes::Aes128;
6use aes_gcm::{
7    Aes128Gcm, Nonce,
8    aead::{Aead, AeadInPlace, KeyInit, Payload},
9};
10use bytes::BytesMut;
11use ctr::cipher::{InnerIvInit, StreamCipher};
12use hmac::{Hmac, Mac};
13use sha1::Sha1;
14use std::collections::HashMap;
15use std::collections::hash_map::Entry;
16use std::fmt;
17
18type Aes128Ctr = ctr::Ctr128BE<Aes128>;
19type HmacSha1 = Hmac<Sha1>;
20
21/// Maximum HMAC-SHA1 digest length (used for fixed-size auth-tag buffers).
22const SHA1_LEN: usize = 20;
23
24/// A received SRTP datagram split into its clear RTP header and protected body.
25/// Unprotection consumes this value and returns a plaintext [`RtpPacket`].
26#[derive(Debug)]
27pub struct SrtpPacket {
28    header: RtpHeader,
29    body: BytesMut,
30    has_padding: bool,
31}
32
33impl SrtpPacket {
34    pub fn parse(mut raw: BytesMut) -> crate::errors::RtpResult<Self> {
35        let (header, has_padding) = RtpHeader::parse(&mut raw)?;
36        Ok(Self {
37            header,
38            body: raw,
39            has_padding,
40        })
41    }
42
43    pub fn header(&self) -> &RtpHeader {
44        &self.header
45    }
46
47    fn marshal_header_into(&self, raw: &mut Vec<u8>) {
48        raw.resize(self.header.encoded_len(), 0);
49        self.header.write_to(self.has_padding, &mut raw[..]);
50    }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum SrtpProfile {
55    #[default]
56    Aes128Sha1_80,
57    Aes128Sha1_32,
58    AeadAes128Gcm,
59    NullCipherHmac,
60}
61
62impl SrtpProfile {
63    fn tag_len(&self) -> usize {
64        match self {
65            Self::Aes128Sha1_80 | Self::NullCipherHmac => 10,
66            Self::Aes128Sha1_32 => 4,
67            Self::AeadAes128Gcm => 16,
68        }
69    }
70
71    fn salt_len(&self) -> usize {
72        match self {
73            Self::AeadAes128Gcm => 12,
74            _ => 14,
75        }
76    }
77
78    fn key_len(&self) -> usize {
79        16
80    }
81
82    fn auth_key_len(&self) -> usize {
83        match self {
84            Self::Aes128Sha1_80 | Self::NullCipherHmac => 20,
85            Self::Aes128Sha1_32 => 20,
86            Self::AeadAes128Gcm => 0, // GCM doesn't use separate auth key
87        }
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct SrtpKeyingMaterial {
93    pub master_key: Vec<u8>,
94    pub master_salt: Vec<u8>,
95}
96
97impl SrtpKeyingMaterial {
98    pub fn new(master_key: Vec<u8>, master_salt: Vec<u8>) -> Self {
99        Self {
100            master_key,
101            master_salt,
102        }
103    }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum SrtpDirection {
108    Sender,
109    Receiver,
110}
111
112pub struct SrtpSession {
113    profile: SrtpProfile,
114    tx_keying: SrtpKeyingMaterial,
115    rx_keying: SrtpKeyingMaterial,
116    tx_contexts: HashMap<u32, SrtpContext>,
117    rx_contexts: HashMap<u32, SrtpContext>,
118}
119
120/// Above this many per-SSRC contexts, stale ones (not seen for
121/// `SSRC_INACTIVITY_EVICT`) are evicted. Caps unbounded growth from SSRC churn
122/// (re-INVITE, simulcast layer switch, SSRC collision, relay rewrite) while
123/// staying well above realistic active-SSRC counts.
124const SSRC_CONTEXT_HIGH_WATERMARK: usize = 32;
125/// Inactivity threshold after which an SRTP context is considered stale and
126/// eligible for eviction. A real media SSRC silent this long has almost
127/// certainly ended (or rotated), so dropping its ROC state is safe.
128const SSRC_INACTIVITY_EVICT: std::time::Duration = std::time::Duration::from_secs(60);
129
130impl SrtpSession {
131    pub fn new(
132        profile: SrtpProfile,
133        tx_keying: SrtpKeyingMaterial,
134        rx_keying: SrtpKeyingMaterial,
135    ) -> Result<Self, SrtpError> {
136        Ok(Self {
137            profile,
138            tx_keying,
139            rx_keying,
140            tx_contexts: HashMap::new(),
141            rx_contexts: HashMap::new(),
142        })
143    }
144
145    pub fn protected_rtp_len(&self, packet: &RtpPacket) -> usize {
146        packet.header.encoded_len()
147            + packet.payload.len()
148            + packet.padding_len as usize
149            + self.profile.tag_len()
150    }
151
152    pub fn protect_rtp(&mut self, packet: &RtpPacket, output: &mut [u8]) -> SrtpResult<()> {
153        let ssrc = packet.header.ssrc;
154        self.evict_stale_tx(ssrc);
155        let ctx = match self.tx_contexts.entry(ssrc) {
156            Entry::Occupied(e) => e.into_mut(),
157            Entry::Vacant(e) => e.insert(SrtpContext::new(
158                ssrc,
159                self.profile,
160                self.tx_keying.clone(),
161                SrtpDirection::Sender,
162            )?),
163        };
164        ctx.last_used = std::time::Instant::now();
165        ctx.protect(packet, output)
166    }
167
168    pub fn unprotect_rtp(&mut self, packet: SrtpPacket) -> SrtpResult<RtpPacket> {
169        let ssrc = packet.header.ssrc;
170        self.evict_stale_rx(ssrc);
171        let ctx = match self.rx_contexts.entry(ssrc) {
172            Entry::Occupied(e) => e.into_mut(),
173            Entry::Vacant(e) => e.insert(SrtpContext::new(
174                ssrc,
175                self.profile,
176                self.rx_keying.clone(),
177                SrtpDirection::Receiver,
178            )?),
179        };
180        ctx.last_used = std::time::Instant::now();
181        ctx.unprotect(packet)
182    }
183
184    pub fn protect_rtcp(&mut self, packet: &mut Vec<u8>) -> SrtpResult<()> {
185        if packet.len() < 8 {
186            return Err(SrtpError::PacketTooShort);
187        }
188        let ssrc = u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]);
189
190        self.evict_stale_tx(ssrc);
191        let ctx = match self.tx_contexts.entry(ssrc) {
192            Entry::Occupied(e) => e.into_mut(),
193            Entry::Vacant(e) => e.insert(SrtpContext::new(
194                ssrc,
195                self.profile,
196                self.tx_keying.clone(),
197                SrtpDirection::Sender,
198            )?),
199        };
200        ctx.last_used = std::time::Instant::now();
201        ctx.protect_rtcp(packet)
202    }
203
204    pub fn unprotect_rtcp(&mut self, packet: &mut Vec<u8>) -> SrtpResult<()> {
205        if packet.len() < 14 {
206            // Header(8) + Index(4) + Tag(>=2)
207            return Err(SrtpError::PacketTooShort);
208        }
209        let ssrc = u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]);
210
211        self.evict_stale_rx(ssrc);
212        let ctx = match self.rx_contexts.entry(ssrc) {
213            Entry::Occupied(e) => e.into_mut(),
214            Entry::Vacant(e) => e.insert(SrtpContext::new(
215                ssrc,
216                self.profile,
217                self.rx_keying.clone(),
218                SrtpDirection::Receiver,
219            )?),
220        };
221        ctx.last_used = std::time::Instant::now();
222        ctx.unprotect_rtcp(packet)
223    }
224
225    /// Evict stale transmit contexts once the map crosses the high-water mark.
226    /// `keep_ssrc` (the SSRC of the packet currently being processed) is never
227    /// evicted.
228    fn evict_stale_tx(&mut self, keep_ssrc: u32) {
229        if self.tx_contexts.len() <= SSRC_CONTEXT_HIGH_WATERMARK {
230            return;
231        }
232        let now = std::time::Instant::now();
233        self.tx_contexts.retain(|s, c| {
234            *s == keep_ssrc || now.duration_since(c.last_used) < SSRC_INACTIVITY_EVICT
235        });
236    }
237
238    /// Evict stale receive contexts once the map crosses the high-water mark.
239    fn evict_stale_rx(&mut self, keep_ssrc: u32) {
240        if self.rx_contexts.len() <= SSRC_CONTEXT_HIGH_WATERMARK {
241            return;
242        }
243        let now = std::time::Instant::now();
244        self.rx_contexts.retain(|s, c| {
245            *s == keep_ssrc || now.duration_since(c.last_used) < SSRC_INACTIVITY_EVICT
246        });
247    }
248}
249
250#[derive(Debug, Clone)]
251struct SessionKeys {
252    cipher_key: Vec<u8>,
253    auth_key: Vec<u8>,
254    salt: Vec<u8>,
255}
256
257#[derive(Clone)]
258pub struct SrtpContext {
259    ssrc: u32,
260    _profile: SrtpProfile,
261    rtp_keys: SessionKeys,
262    rtcp_keys: SessionKeys,
263    /// Pre-expanded AES-128 round keys for the RTP session cipher key. Building
264    /// this once avoids re-running the AES key schedule on every packet (the
265    /// `ctr` cipher is reconstructed per packet from this cached key + a
266    /// per-packet IV, which is a cheap clone of the round keys, not a re-key).
267    rtp_aes_key: Aes128,
268    rtcp_aes_key: Aes128,
269    rtp_gcm_cipher: Option<Aes128Gcm>,
270    rtcp_gcm_cipher: Option<Aes128Gcm>,
271    rtp_auth_prototype: Option<HmacSha1>,
272    rtcp_auth_prototype: Option<HmacSha1>,
273    direction: SrtpDirection,
274    rollover_counter: u32,
275    last_sequence: Option<u16>,
276    rtcp_index: u32,
277    /// Reusable receive-side scratch buffer holding the reconstructed clear RTP
278    /// header for authentication after the protected body has been split off.
279    auth_scratch: Vec<u8>,
280    /// Wall-clock time of the most recent protect/unprotect call, used to evict
281    /// contexts for SSRCs that have gone away (prevents unbounded growth as
282    /// SSRCs churn across a long call / relay).
283    last_used: std::time::Instant,
284}
285
286impl fmt::Debug for SrtpContext {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        f.debug_struct("SrtpContext")
289            .field("ssrc", &self.ssrc)
290            .field("_profile", &self._profile)
291            .field("direction", &self.direction)
292            .field("rollover_counter", &self.rollover_counter)
293            .finish()
294    }
295}
296
297impl SrtpContext {
298    pub fn new(
299        ssrc: u32,
300        profile: SrtpProfile,
301        keying: SrtpKeyingMaterial,
302        direction: SrtpDirection,
303    ) -> SrtpResult<Self> {
304        if keying.master_key.len() < profile.key_len()
305            || keying.master_salt.len() < profile.salt_len()
306        {
307            return Err(SrtpError::UnsupportedProfile);
308        }
309
310        let (rtp_keys, rtcp_keys) = Self::derive_keys(profile, &keying)?;
311
312        // Pre-expand the AES-128 key schedules once (instead of per packet).
313        let mut rtp_key_bytes = [0u8; 16];
314        rtp_key_bytes.copy_from_slice(&rtp_keys.cipher_key[..16]);
315        let mut rtcp_key_bytes = [0u8; 16];
316        rtcp_key_bytes.copy_from_slice(&rtcp_keys.cipher_key[..16]);
317        let rtp_aes_key = <Aes128 as ctr::cipher::KeyInit>::new(&rtp_key_bytes.into());
318        let rtcp_aes_key = <Aes128 as ctr::cipher::KeyInit>::new(&rtcp_key_bytes.into());
319
320        let rtp_gcm_cipher = if let SrtpProfile::AeadAes128Gcm = profile {
321            Some(
322                Aes128Gcm::new_from_slice(&rtp_keys.cipher_key)
323                    .map_err(|_| SrtpError::UnsupportedProfile)?,
324            )
325        } else {
326            None
327        };
328
329        let rtcp_gcm_cipher = if let SrtpProfile::AeadAes128Gcm = profile {
330            Some(
331                Aes128Gcm::new_from_slice(&rtcp_keys.cipher_key)
332                    .map_err(|_| SrtpError::UnsupportedProfile)?,
333            )
334        } else {
335            None
336        };
337
338        let rtp_auth_prototype = if !rtp_keys.auth_key.is_empty() {
339            Some(
340                <HmacSha1 as hmac::digest::KeyInit>::new_from_slice(&rtp_keys.auth_key)
341                    .map_err(|_| SrtpError::UnsupportedProfile)?,
342            )
343        } else {
344            None
345        };
346
347        let rtcp_auth_prototype = if !rtcp_keys.auth_key.is_empty() {
348            Some(
349                <HmacSha1 as hmac::digest::KeyInit>::new_from_slice(&rtcp_keys.auth_key)
350                    .map_err(|_| SrtpError::UnsupportedProfile)?,
351            )
352        } else {
353            None
354        };
355
356        Ok(Self {
357            ssrc,
358            _profile: profile,
359            rtp_keys,
360            rtcp_keys,
361            rtp_aes_key,
362            rtcp_aes_key,
363            rtp_gcm_cipher,
364            rtcp_gcm_cipher,
365            rtp_auth_prototype,
366            rtcp_auth_prototype,
367            direction,
368            rollover_counter: 0,
369            last_sequence: None,
370            rtcp_index: 0,
371            auth_scratch: Vec::new(),
372            last_used: std::time::Instant::now(),
373        })
374    }
375
376    fn derive_keys(
377        profile: SrtpProfile,
378        keying: &SrtpKeyingMaterial,
379    ) -> SrtpResult<(SessionKeys, SessionKeys)> {
380        let key_len = profile.key_len();
381        let salt_len = profile.salt_len();
382        let auth_len = profile.auth_key_len();
383
384        // RTP Keys
385        let rtp_cipher = Self::kdf(key_len, 0x00, &keying.master_key, &keying.master_salt)?;
386        let rtp_auth = if auth_len > 0 {
387            Self::kdf(auth_len, 0x01, &keying.master_key, &keying.master_salt)?
388        } else {
389            Vec::new()
390        };
391        let rtp_salt = Self::kdf(salt_len, 0x02, &keying.master_key, &keying.master_salt)?;
392
393        // RTCP Keys
394        let rtcp_cipher = Self::kdf(key_len, 0x03, &keying.master_key, &keying.master_salt)?;
395        let rtcp_auth = if auth_len > 0 {
396            Self::kdf(auth_len, 0x04, &keying.master_key, &keying.master_salt)?
397        } else {
398            Vec::new()
399        };
400        let rtcp_salt = Self::kdf(salt_len, 0x05, &keying.master_key, &keying.master_salt)?;
401
402        Ok((
403            SessionKeys {
404                cipher_key: rtp_cipher,
405                auth_key: rtp_auth,
406                salt: rtp_salt,
407            },
408            SessionKeys {
409                cipher_key: rtcp_cipher,
410                auth_key: rtcp_auth,
411                salt: rtcp_salt,
412            },
413        ))
414    }
415
416    fn kdf(len: usize, label: u8, master_key: &[u8], master_salt: &[u8]) -> SrtpResult<Vec<u8>> {
417        // RFC 3711 Section 4.3. Key Derivation
418        // AES-CM PRF
419        // x = (label << 48) XOR master_salt
420        // We assume r=0 (index) for session keys.
421
422        let mut iv = [0u8; 16];
423        // Copy salt (14 bytes)
424        for (i, &b) in master_salt.iter().take(14).enumerate() {
425            iv[i] = b;
426        }
427
428        // XOR label into byte 7 (see discussion on bit layout)
429        // This matches libsrtp and other implementations for the standard layout
430        iv[7] ^= label;
431
432        // Run AES-CM
433        let mut out = vec![0u8; len];
434        let mut cipher = <Aes128Ctr as ctr::cipher::KeyIvInit>::new_from_slices(&master_key[..16], &iv)
435            .map_err(|_| SrtpError::UnsupportedProfile)?;
436        cipher.apply_keystream(&mut out);
437
438        Ok(out)
439    }
440
441    pub fn protect_rtcp(&mut self, packet: &mut Vec<u8>) -> SrtpResult<()> {
442        self.rtcp_index += 1;
443        let index = self.rtcp_index;
444        // E-bit = 1 (Encrypted)
445        let index_with_e = index | 0x8000_0000;
446
447        if let SrtpProfile::AeadAes128Gcm = self._profile {
448            let nonce = self.build_gcm_rtcp_nonce(index);
449            let cipher = self
450                .rtcp_gcm_cipher
451                .as_ref()
452                .ok_or(SrtpError::UnsupportedProfile)?;
453
454            // AAD = Header (8 bytes) || Index (4 bytes, WITH E-bit)
455            let mut aad = Vec::with_capacity(12);
456            aad.extend_from_slice(&packet[..8]);
457            aad.extend_from_slice(&index_with_e.to_be_bytes());
458
459            // Payload = Packet body (after header)
460            let payload_data = &packet[8..];
461
462            let payload = Payload {
463                msg: payload_data,
464                aad: &aad,
465            };
466
467            let ciphertext = cipher
468                .encrypt(Nonce::from_slice(&nonce), payload)
469                .map_err(|_| SrtpError::AuthenticationFailed)?;
470
471            // Reconstruct packet: Header || Ciphertext || Index
472            packet.truncate(8);
473            packet.extend_from_slice(&ciphertext);
474            packet.extend_from_slice(&index_with_e.to_be_bytes());
475
476            return Ok(());
477        }
478
479        // Encrypt payload (everything after first 8 bytes of header)
480        // RFC 3711: The first 8 octets of the RTCP header are not encrypted.
481        if packet.len() > 8 {
482            self.cipher_rtcp(packet, index);
483        }
484
485        // Append SRTCP Index
486        packet.extend_from_slice(&index_with_e.to_be_bytes());
487
488        // Authenticate
489        let mut tag = [0u8; SHA1_LEN];
490        self.auth_tag_rtcp_into(packet, &mut tag)?;
491        packet.extend_from_slice(&tag[..self._profile.tag_len()]);
492
493        Ok(())
494    }
495
496    pub fn unprotect_rtcp(&mut self, packet: &mut Vec<u8>) -> SrtpResult<()> {
497        let tag_len = self._profile.tag_len();
498        if packet.len() < tag_len + 4 {
499            return Err(SrtpError::PacketTooShort);
500        }
501
502        if let SrtpProfile::AeadAes128Gcm = self._profile {
503            // Read Index
504            let index_bytes = &packet[packet.len() - 4..];
505            let index_with_e = u32::from_be_bytes([
506                index_bytes[0],
507                index_bytes[1],
508                index_bytes[2],
509                index_bytes[3],
510            ]);
511            let index = index_with_e & 0x7FFF_FFFF;
512
513            // Replay check
514            if index > self.rtcp_index {
515                self.rtcp_index = index;
516            }
517
518            let nonce = self.build_gcm_rtcp_nonce(index);
519            let cipher = self
520                .rtcp_gcm_cipher
521                .as_ref()
522                .ok_or(SrtpError::UnsupportedProfile)?;
523
524            // AAD = Header (8 bytes) || Index (4 bytes, WITH E-bit)
525            let mut aad = Vec::with_capacity(12);
526            aad.extend_from_slice(&packet[..8]);
527            aad.extend_from_slice(&index_with_e.to_be_bytes());
528
529            // Ciphertext = Packet body (after header, before index)
530            // Note: Tag is appended to ciphertext in GCM encrypt output.
531            // So Ciphertext + Tag is what we have between Header and Index.
532            let ciphertext_and_tag = &packet[8..packet.len() - 4];
533
534            let payload = Payload {
535                msg: ciphertext_and_tag,
536                aad: &aad,
537            };
538
539            let plaintext = cipher
540                .decrypt(Nonce::from_slice(&nonce), payload)
541                .map_err(|_| SrtpError::AuthenticationFailed)?;
542
543            // Reconstruct packet: Header || Plaintext
544            packet.truncate(8);
545            packet.extend_from_slice(&plaintext);
546
547            return Ok(());
548        }
549
550        // Split tag
551        let split = packet.len() - tag_len;
552        let mut tag = [0u8; SHA1_LEN];
553        tag[..tag_len].copy_from_slice(&packet[split..split + tag_len]);
554        packet.truncate(split);
555
556        // Verify tag
557        let mut expected = [0u8; SHA1_LEN];
558        self.auth_tag_rtcp_into(packet, &mut expected)?;
559        if !constant_time_eq(&tag[..tag_len], &expected[..tag_len]) {
560            return Err(SrtpError::AuthenticationFailed);
561        }
562
563        // Read Index
564        let index_bytes = &packet[packet.len() - 4..];
565        let index_with_e = u32::from_be_bytes([
566            index_bytes[0],
567            index_bytes[1],
568            index_bytes[2],
569            index_bytes[3],
570        ]);
571        packet.truncate(packet.len() - 4);
572
573        let e_bit = (index_with_e & 0x8000_0000) != 0;
574        let index = index_with_e & 0x7FFF_FFFF;
575
576        // Replay check (simplified: just check if index is newer than last seen?)
577        // For now, we just update.
578        if index > self.rtcp_index {
579            self.rtcp_index = index;
580        }
581
582        if e_bit && packet.len() > 8 {
583            self.cipher_rtcp(packet, index);
584        }
585
586        Ok(())
587    }
588
589    /// Build an AES-128-CTR cipher from a pre-expanded key schedule and a
590    /// per-packet IV. This reuses the cached round keys (a cheap clone) instead
591    /// of re-running the AES key expansion on every packet.
592    #[inline]
593    fn ctr_from_key(key: &Aes128, iv: [u8; 16]) -> Aes128Ctr {
594        let core = <ctr::CtrCore<Aes128, ctr::flavors::Ctr128BE> as InnerIvInit>::inner_iv_init(
595            key.clone(),
596            &iv.into(),
597        );
598        Aes128Ctr::from_core(core)
599    }
600
601    fn cipher_rtcp(&self, packet: &mut [u8], index: u32) {
602        // IV = (salt * 2^16) XOR (SSRC * 2^64) XOR (SRTCP_INDEX * 2^16)
603        let mut iv = [0u8; 16];
604        iv[..14].copy_from_slice(&self.rtcp_keys.salt[..14]);
605
606        let mut block = [0u8; 16];
607        block[4..8].copy_from_slice(&self.ssrc.to_be_bytes());
608        block[10..14].copy_from_slice(&index.to_be_bytes());
609
610        for (a, &b) in iv.iter_mut().zip(block.iter()) {
611            *a ^= b;
612        }
613
614        // Reuse the cached AES key schedule (a clone of the expanded round
615        // keys) instead of re-running AES key expansion on every RTCP packet.
616        let mut cipher = Self::ctr_from_key(&self.rtcp_aes_key, iv);
617        cipher.apply_keystream(&mut packet[8..]);
618    }
619
620    /// Compute the RTCP auth tag (HMAC-SHA1, truncated) into `out`, reusing the
621    /// cached HMAC prototype to avoid re-padding the key (and a `Vec` alloc) on
622    /// every RTCP packet.
623    fn auth_tag_rtcp_into(&self, data: &[u8], out: &mut [u8; SHA1_LEN]) -> SrtpResult<()> {
624        let mut mac = self
625            .rtcp_auth_prototype
626            .as_ref()
627            .ok_or(SrtpError::UnsupportedProfile)?
628            .clone();
629        mac.update(data);
630        out.copy_from_slice(&mac.finalize().into_bytes());
631        Ok(())
632    }
633
634    pub fn protected_rtp_len(&self, packet: &RtpPacket) -> usize {
635        packet.header.encoded_len()
636            + packet.payload.len()
637            + packet.padding_len as usize
638            + self._profile.tag_len()
639    }
640
641    pub fn protect(&mut self, packet: &RtpPacket, output: &mut [u8]) -> SrtpResult<()> {
642        packet.header.validate()?;
643        let sequence_number = packet.header.sequence_number;
644        let roc = self.estimate_roc(sequence_number);
645        let tag_len = self._profile.tag_len();
646        let header_len = packet.header.encoded_len();
647        let body_len = packet.payload.len() + packet.padding_len as usize;
648        let body_end = header_len + body_len;
649        let protected_len = body_end + tag_len;
650
651        if output.len() != protected_len {
652            return Err(SrtpError::Internal(format!(
653                "protected RTP output length mismatch: expected {protected_len}, got {}",
654                output.len()
655            )));
656        }
657
658        packet
659            .header
660            .write_to(packet.padding_len != 0, &mut output[..header_len]);
661        output[header_len..header_len + packet.payload.len()].copy_from_slice(&packet.payload);
662        if packet.padding_len != 0 {
663            output[header_len + packet.payload.len()..body_end].fill(packet.padding_len);
664        }
665
666        if let SrtpProfile::AeadAes128Gcm = self._profile {
667            let nonce = self.build_gcm_nonce(sequence_number, roc);
668            let cipher = self
669                .rtp_gcm_cipher
670                .as_ref()
671                .ok_or(SrtpError::UnsupportedProfile)?;
672            let (header, protected_body) = output.split_at_mut(header_len);
673            let (body, tag_output) = protected_body.split_at_mut(body_len);
674            let tag = cipher
675                .encrypt_in_place_detached(Nonce::from_slice(&nonce), header, body)
676                .map_err(|_| SrtpError::AuthenticationFailed)?;
677            tag_output.copy_from_slice(&tag);
678        } else {
679            let encrypts = !matches!(self._profile, SrtpProfile::NullCipherHmac);
680            if body_len != 0 && encrypts {
681                let iv = self.build_iv(sequence_number, roc);
682                let mut cipher = Self::ctr_from_key(&self.rtp_aes_key, iv);
683                cipher.apply_keystream(&mut output[header_len..body_end]);
684            }
685
686            let mut mac = self
687                .rtp_auth_prototype
688                .as_ref()
689                .ok_or(SrtpError::UnsupportedProfile)?
690                .clone();
691            mac.update(&output[..body_end]);
692            mac.update(&roc.to_be_bytes());
693            let result = mac.finalize().into_bytes();
694            output[body_end..].copy_from_slice(&result[..tag_len]);
695        }
696
697        self.update(sequence_number, roc);
698        Ok(())
699    }
700
701    pub fn unprotect(&mut self, mut packet: SrtpPacket) -> SrtpResult<RtpPacket> {
702        let tag_len = self._profile.tag_len();
703        if packet.body.len() < tag_len {
704            return Err(SrtpError::PacketTooShort);
705        }
706
707        let sequence_number = packet.header.sequence_number;
708        let roc = self.estimate_roc(sequence_number);
709        packet.marshal_header_into(&mut self.auth_scratch);
710
711        if let SrtpProfile::AeadAes128Gcm = self._profile {
712            let nonce = self.build_gcm_nonce(sequence_number, roc);
713            let cipher = self
714                .rtp_gcm_cipher
715                .as_ref()
716                .ok_or(SrtpError::UnsupportedProfile)?;
717            let split = packet.body.len() - tag_len;
718            let tag = aes_gcm::Tag::clone_from_slice(&packet.body[split..]);
719            packet.body.truncate(split);
720            cipher
721                .decrypt_in_place_detached(
722                    Nonce::from_slice(&nonce),
723                    &self.auth_scratch,
724                    &mut packet.body,
725                    &tag,
726                )
727                .map_err(|_| SrtpError::AuthenticationFailed)?;
728        } else {
729            let split = packet.body.len() - tag_len;
730            if let Some(proto) = self.rtp_auth_prototype.as_ref() {
731                let mut mac = proto.clone();
732                mac.update(&self.auth_scratch);
733                mac.update(&packet.body[..split]);
734                mac.update(&roc.to_be_bytes());
735                let result = mac.finalize().into_bytes();
736                if !constant_time_eq(&packet.body[split..], &result[..tag_len]) {
737                    return Err(SrtpError::AuthenticationFailed);
738                }
739            }
740            packet.body.truncate(split);
741
742            let decrypts = !matches!(self._profile, SrtpProfile::NullCipherHmac);
743            if !packet.body.is_empty() && decrypts {
744                let iv = self.build_iv(sequence_number, roc);
745                let mut cipher = Self::ctr_from_key(&self.rtp_aes_key, iv);
746                cipher.apply_keystream(&mut packet.body);
747            }
748        }
749
750        let padding_len = if packet.has_padding {
751            let padding_len = *packet.body.last().ok_or(SrtpError::PacketTooShort)?;
752            if padding_len == 0 || padding_len as usize > packet.body.len() {
753                return Err(SrtpError::Internal(
754                    "invalid decrypted RTP padding length".to_string(),
755                ));
756            }
757            packet.body.truncate(packet.body.len() - padding_len as usize);
758            padding_len
759        } else {
760            0
761        };
762
763        self.update(sequence_number, roc);
764        Ok(RtpPacket {
765            header: packet.header,
766            payload: packet.body.freeze(),
767            padding_len,
768        })
769    }
770
771    fn build_gcm_rtcp_nonce(&self, index: u32) -> [u8; 12] {
772        let mut iv = [0u8; 12];
773        iv.copy_from_slice(&self.rtcp_keys.salt[..12]);
774
775        let mut block = [0u8; 12];
776        block[2..6].copy_from_slice(&self.ssrc.to_be_bytes());
777        block[8..12].copy_from_slice(&index.to_be_bytes());
778
779        for i in 0..12 {
780            iv[i] ^= block[i];
781        }
782        iv
783    }
784
785    fn build_gcm_nonce(&self, sequence: u16, roc: u32) -> [u8; 12] {
786        let mut iv = [0u8; 12];
787        iv.copy_from_slice(&self.rtp_keys.salt[..12]);
788
789        let mut block = [0u8; 12];
790        block[2..6].copy_from_slice(&self.ssrc.to_be_bytes());
791        block[6..10].copy_from_slice(&roc.to_be_bytes());
792        block[10..12].copy_from_slice(&sequence.to_be_bytes());
793
794        for i in 0..12 {
795            iv[i] ^= block[i];
796        }
797        iv
798    }
799
800    fn build_iv(&self, sequence: u16, roc: u32) -> [u8; 16] {
801        let index = ((roc as u64) << 16) | sequence as u64;
802        let mut iv = [0u8; 16];
803        iv[..14].copy_from_slice(&self.rtp_keys.salt[..14]);
804
805        let mut block = [0u8; 16];
806        block[4..8].copy_from_slice(&self.ssrc.to_be_bytes());
807
808        // IV = (salt * 2^16) XOR (SSRC * 2^64) XOR (Index * 2^16)
809        let iv_part = index << 16;
810        block[8..16].copy_from_slice(&iv_part.to_be_bytes());
811
812        for (a, &b) in iv.iter_mut().zip(block.iter()) {
813            *a ^= b;
814        }
815        iv
816    }
817
818    fn estimate_roc(&self, sequence: u16) -> u32 {
819        let Some(last_seq) = self.last_sequence else {
820            return self.rollover_counter;
821        };
822
823        let roc = self.rollover_counter;
824        let diff = (sequence as i32) - (last_seq as i32);
825
826        if diff < -32768 {
827            roc.wrapping_add(1)
828        } else if diff > 32768 {
829            roc.wrapping_sub(1)
830        } else {
831            roc
832        }
833    }
834
835    fn update(&mut self, sequence: u16, roc: u32) {
836        if self.last_sequence.is_none() {
837            self.last_sequence = Some(sequence);
838            self.rollover_counter = roc;
839            return;
840        }
841
842        let current_index =
843            ((self.rollover_counter as u64) << 16) | (self.last_sequence.unwrap() as u64);
844        let new_index = ((roc as u64) << 16) | (sequence as u64);
845
846        if new_index > current_index {
847            self.rollover_counter = roc;
848            self.last_sequence = Some(sequence);
849        }
850    }
851
852    pub fn ssrc(&self) -> u32 {
853        self.ssrc
854    }
855
856    pub fn direction(&self) -> SrtpDirection {
857        self.direction
858    }
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864    use crate::rtp::{RtpHeader, RtpHeaderExtension, RtpPacket};
865
866    fn sample_packet(seq: u16) -> RtpPacket {
867        let header = RtpHeader::new(96, seq, 1234, 0xdead_beef);
868        RtpPacket::new(header, vec![1, 2, 3])
869    }
870
871    fn material() -> SrtpKeyingMaterial {
872        SrtpKeyingMaterial::new(vec![0; 16], vec![0; 14])
873    }
874
875    #[test]
876    fn protect_and_unprotect_roundtrip() {
877        let mut session =
878            SrtpSession::new(SrtpProfile::Aes128Sha1_80, material(), material()).unwrap();
879        let packet = sample_packet(1);
880        let original = packet.payload.clone();
881        let mut raw = BytesMut::new();
882        raw.resize(session.protected_rtp_len(&packet), 0);
883        session.protect_rtp(&packet, &mut raw).unwrap();
884        let header_len = packet.header.encoded_len();
885        assert_eq!(raw.len(), header_len + original.len() + 10);
886        assert_ne!(raw[header_len..header_len + original.len()], original[..]);
887        let packet = SrtpPacket::parse(raw).unwrap();
888        let packet = session.unprotect_rtp(packet).unwrap();
889        assert_eq!(packet.payload, original);
890    }
891
892    #[test]
893    fn protect_and_unprotect_roundtrip_gcm() {
894        let mut session =
895            SrtpSession::new(SrtpProfile::AeadAes128Gcm, material(), material()).unwrap();
896        let packet = sample_packet(1);
897        let original = packet.payload.clone();
898        let mut raw = BytesMut::new();
899        raw.resize(session.protected_rtp_len(&packet), 0);
900        session.protect_rtp(&packet, &mut raw).unwrap();
901        let header_len = packet.header.encoded_len();
902        assert_eq!(raw.len(), header_len + original.len() + 16);
903        assert_ne!(raw[header_len..header_len + original.len()], original[..]);
904        let packet = SrtpPacket::parse(raw).unwrap();
905        let packet = session.unprotect_rtp(packet).unwrap();
906        assert_eq!(packet.payload, original);
907    }
908
909    #[test]
910    fn aes_cm_padding_is_encrypted_and_resolved_after_parsing() {
911        let mut sender = SrtpContext::new(
912            0xdead_beef,
913            SrtpProfile::Aes128Sha1_80,
914            material(),
915            SrtpDirection::Sender,
916        )
917        .unwrap();
918        let mut receiver =
919            SrtpSession::new(SrtpProfile::Aes128Sha1_80, material(), material()).unwrap();
920        let mut packet = sample_packet(7);
921        packet.header.extension = Some(RtpHeaderExtension::new(0xBEDE, vec![1, 2, 3, 4]));
922        packet.padding_len = 4;
923        let original = packet.payload.clone();
924
925        let mut raw = BytesMut::new();
926        raw.resize(sender.protected_rtp_len(&packet), 0);
927        sender.protect(&packet, &mut raw).unwrap();
928        let packet = SrtpPacket::parse(raw).unwrap();
929        let body_ptr = packet.body.as_ptr();
930        let packet = receiver.unprotect_rtp(packet).unwrap();
931
932        assert_eq!(packet.payload, original);
933        assert_eq!(packet.padding_len, 4);
934        assert_eq!(packet.payload.as_ptr(), body_ptr);
935    }
936
937    #[test]
938    fn gcm_padding_is_encrypted_and_resolved_after_parsing() {
939        let mut sender = SrtpContext::new(
940            0xdead_beef,
941            SrtpProfile::AeadAes128Gcm,
942            material(),
943            SrtpDirection::Sender,
944        )
945        .unwrap();
946        let mut receiver =
947            SrtpSession::new(SrtpProfile::AeadAes128Gcm, material(), material()).unwrap();
948        let mut packet = sample_packet(8);
949        packet.padding_len = 4;
950        let original = packet.payload.clone();
951
952        let mut raw = BytesMut::new();
953        raw.resize(sender.protected_rtp_len(&packet), 0);
954        sender.protect(&packet, &mut raw).unwrap();
955        let packet = SrtpPacket::parse(raw).unwrap();
956        let body_ptr = packet.body.as_ptr();
957        let packet = receiver.unprotect_rtp(packet).unwrap();
958
959        assert_eq!(packet.payload, original);
960        assert_eq!(packet.padding_len, 4);
961        assert_eq!(packet.payload.as_ptr(), body_ptr);
962    }
963
964    #[test]
965    fn authentication_failure_returns_error() {
966        let mut ctx = SrtpContext::new(
967            42,
968            SrtpProfile::Aes128Sha1_80,
969            material(),
970            SrtpDirection::Receiver,
971        )
972        .unwrap();
973        let packet = sample_packet(1);
974        let mut raw = BytesMut::new();
975        raw.resize(ctx.protected_rtp_len(&packet), 0);
976        ctx.protect(&packet, &mut raw).unwrap();
977        let mut packet = SrtpPacket::parse(raw).unwrap();
978        packet.body[0] ^= 0xFF;
979        let err = ctx.unprotect(packet).unwrap_err();
980        assert!(matches!(err, SrtpError::AuthenticationFailed));
981    }
982
983    #[test]
984    fn null_cipher_still_authenticates() {
985        let mut ctx = SrtpContext::new(
986            7,
987            SrtpProfile::NullCipherHmac,
988            material(),
989            SrtpDirection::Sender,
990        )
991        .unwrap();
992        let packet = sample_packet(10);
993        let mut raw = BytesMut::new();
994        raw.resize(ctx.protected_rtp_len(&packet), 0);
995        ctx.protect(&packet, &mut raw).unwrap();
996        assert_eq!(raw.len(), packet.header.encoded_len() + 3 + 10);
997    }
998
999    #[test]
1000    fn roc_rollover_handling() {
1001        let mut sender =
1002            SrtpSession::new(SrtpProfile::Aes128Sha1_80, material(), material()).unwrap();
1003        let mut receiver =
1004            SrtpSession::new(SrtpProfile::Aes128Sha1_80, material(), material()).unwrap();
1005
1006        let packet = sample_packet(65535);
1007        let mut raw = BytesMut::new();
1008        raw.resize(sender.protected_rtp_len(&packet), 0);
1009        sender.protect_rtp(&packet, &mut raw).unwrap();
1010        let p1 = SrtpPacket::parse(raw).unwrap();
1011
1012        let packet = sample_packet(0);
1013        let mut raw = BytesMut::new();
1014        raw.resize(sender.protected_rtp_len(&packet), 0);
1015        sender.protect_rtp(&packet, &mut raw).unwrap();
1016        let p2 = SrtpPacket::parse(raw).unwrap();
1017
1018        // Receive in order
1019        receiver.unprotect_rtp(p1).unwrap();
1020        receiver.unprotect_rtp(p2).unwrap();
1021    }
1022
1023    #[test]
1024    fn roc_rollover_reordered() {
1025        let mut sender =
1026            SrtpSession::new(SrtpProfile::Aes128Sha1_80, material(), material()).unwrap();
1027        let mut receiver =
1028            SrtpSession::new(SrtpProfile::Aes128Sha1_80, material(), material()).unwrap();
1029
1030        let packet = sample_packet(50000);
1031        let mut raw = BytesMut::new();
1032        raw.resize(sender.protected_rtp_len(&packet), 0);
1033        sender.protect_rtp(&packet, &mut raw).unwrap();
1034        let p0 = SrtpPacket::parse(raw).unwrap();
1035        receiver.unprotect_rtp(p0).unwrap();
1036
1037        let packet = sample_packet(65535);
1038        let mut raw = BytesMut::new();
1039        raw.resize(sender.protected_rtp_len(&packet), 0);
1040        sender.protect_rtp(&packet, &mut raw).unwrap();
1041        let p1 = SrtpPacket::parse(raw).unwrap();
1042
1043        let packet = sample_packet(0);
1044        let mut raw = BytesMut::new();
1045        raw.resize(sender.protected_rtp_len(&packet), 0);
1046        sender.protect_rtp(&packet, &mut raw).unwrap();
1047        let p2 = SrtpPacket::parse(raw).unwrap();
1048
1049        // Receive out of order: p2 (seq 0) then p1 (seq 65535)
1050
1051        receiver.unprotect_rtp(p2).unwrap();
1052        receiver.unprotect_rtp(p1).unwrap();
1053    }
1054}
1055
1056fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
1057    if a.len() != b.len() {
1058        return false;
1059    }
1060    let mut diff = 0u8;
1061    for (x, y) in a.iter().zip(b.iter()) {
1062        diff |= x ^ y;
1063    }
1064    diff == 0
1065}
1066
1067#[cfg(test)]
1068mod security_tests {
1069    use super::*;
1070    use crate::rtp::{RtpHeader, RtpPacket};
1071
1072    fn sample_packet(seq: u16) -> RtpPacket {
1073        let header = RtpHeader::new(96, seq, 1234, 0xdead_beef);
1074        RtpPacket::new(header, vec![1, 2, 3])
1075    }
1076
1077    #[test]
1078    fn default_profile_is_encrypting_not_null() {
1079        // Security: default must be an encrypting profile, never NullCipherHmac
1080        let default_profile = SrtpProfile::default();
1081        assert_eq!(
1082            default_profile,
1083            SrtpProfile::Aes128Sha1_80,
1084            "Default SRTP profile must be Aes128Sha1_80 (encrypting), not NullCipherHmac"
1085        );
1086    }
1087
1088    #[test]
1089    fn null_cipher_must_be_explicit() {
1090        // NullCipherHmac should never be selected accidentally
1091        let profiles = [
1092            SrtpProfile::default(),
1093            SrtpProfile::Aes128Sha1_80,
1094            SrtpProfile::Aes128Sha1_32,
1095            SrtpProfile::AeadAes128Gcm,
1096        ];
1097        for p in &profiles {
1098            assert_ne!(
1099                *p,
1100                SrtpProfile::NullCipherHmac,
1101                "Production profiles must exclude NullCipherHmac: {:?}",
1102                p
1103            );
1104        }
1105    }
1106
1107    #[test]
1108    fn null_cipher_protect_is_transparent_but_authenticates() {
1109        // NullCipherHmac adds auth tag but doesn't encrypt payload
1110        let mut ctx = SrtpContext::new(
1111            42,
1112            SrtpProfile::NullCipherHmac,
1113            SrtpKeyingMaterial::new(vec![0; 16], vec![0; 14]),
1114            SrtpDirection::Sender,
1115        )
1116        .unwrap();
1117        let packet = sample_packet(100);
1118        let original_payload = packet.payload.clone();
1119        let mut raw = BytesMut::new();
1120        raw.resize(ctx.protected_rtp_len(&packet), 0);
1121        ctx.protect(&packet, &mut raw).unwrap();
1122        let header_len = packet.header.encoded_len();
1123        assert_eq!(raw.len(), header_len + original_payload.len() + 10);
1124        assert_eq!(
1125            &raw[header_len..header_len + original_payload.len()],
1126            &original_payload[..]
1127        );
1128        // Must still verify
1129        let mut rx_ctx = SrtpContext::new(
1130            42,
1131            SrtpProfile::NullCipherHmac,
1132            SrtpKeyingMaterial::new(vec![0; 16], vec![0; 14]),
1133            SrtpDirection::Receiver,
1134        )
1135        .unwrap();
1136        let packet = SrtpPacket::parse(raw).unwrap();
1137        let packet = rx_ctx.unprotect(packet).unwrap();
1138        assert_eq!(packet.payload, original_payload);
1139    }
1140}