Skip to main content

rustrtc/
srtp.rs

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