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
21const SHA1_LEN: usize = 20;
23
24#[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, }
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
120const SSRC_CONTEXT_HIGH_WATERMARK: usize = 32;
125const 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 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 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 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 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 auth_scratch: Vec<u8>,
280 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 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 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 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 let mut iv = [0u8; 16];
423 for (i, &b) in master_salt.iter().take(14).enumerate() {
425 iv[i] = b;
426 }
427
428 iv[7] ^= label;
431
432 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 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 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 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 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 if packet.len() > 8 {
482 self.cipher_rtcp(packet, index);
483 }
484
485 packet.extend_from_slice(&index_with_e.to_be_bytes());
487
488 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 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 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 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 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 packet.truncate(8);
545 packet.extend_from_slice(&plaintext);
546
547 return Ok(());
548 }
549
550 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 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 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 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 #[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 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 let mut cipher = Self::ctr_from_key(&self.rtcp_aes_key, iv);
617 cipher.apply_keystream(&mut packet[8..]);
618 }
619
620 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 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 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 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 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 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 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 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}