1use std::collections::HashMap;
19use std::fmt;
20
21use aes::Aes128;
22use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
23use base64::Engine as _;
24use hmac::{Hmac, Mac};
25use sha1::Sha1;
26
27type HmacSha1 = Hmac<Sha1>;
28
29const MASTER_KEY_LEN: usize = 16;
30const MASTER_SALT_LEN: usize = 14;
31const AUTH_KEY_LEN: usize = 20;
32const REPLAY_WINDOW: u64 = 64;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[non_exhaustive]
38pub enum SrtpSuite {
39 AesCm128HmacSha1_80,
41 AesCm128HmacSha1_32,
43}
44
45impl SrtpSuite {
46 pub fn name(self) -> &'static str {
48 match self {
49 Self::AesCm128HmacSha1_80 => "AES_CM_128_HMAC_SHA1_80",
50 Self::AesCm128HmacSha1_32 => "AES_CM_128_HMAC_SHA1_32",
51 }
52 }
53
54 pub fn from_name(name: &str) -> Option<Self> {
56 match name {
57 "AES_CM_128_HMAC_SHA1_80" => Some(Self::AesCm128HmacSha1_80),
58 "AES_CM_128_HMAC_SHA1_32" => Some(Self::AesCm128HmacSha1_32),
59 _ => None,
60 }
61 }
62
63 pub fn tag_len(self) -> usize {
65 match self {
66 Self::AesCm128HmacSha1_80 => 10,
67 Self::AesCm128HmacSha1_32 => 4,
68 }
69 }
70}
71
72#[derive(Clone, PartialEq, Eq)]
74pub struct MasterKey {
75 key: [u8; MASTER_KEY_LEN],
76 salt: [u8; MASTER_SALT_LEN],
77}
78
79impl MasterKey {
80 pub fn new(key: [u8; MASTER_KEY_LEN], salt: [u8; MASTER_SALT_LEN]) -> Self {
82 Self { key, salt }
83 }
84
85 pub fn generate() -> std::io::Result<Self> {
87 let mut buf = [std::mem::MaybeUninit::<u8>::uninit(); MASTER_KEY_LEN + MASTER_SALT_LEN];
88 let bytes = getrandom::getrandom_uninit(&mut buf).map_err(std::io::Error::other)?;
89 Ok(Self::from_bytes(bytes).expect("the length is right"))
90 }
91
92 fn from_bytes(bytes: &[u8]) -> Option<Self> {
93 if bytes.len() != MASTER_KEY_LEN + MASTER_SALT_LEN {
94 return None;
95 }
96 Some(Self {
97 key: bytes[..MASTER_KEY_LEN].try_into().ok()?,
98 salt: bytes[MASTER_KEY_LEN..].try_into().ok()?,
99 })
100 }
101
102 pub fn to_inline(&self) -> String {
104 let mut bytes = Vec::with_capacity(MASTER_KEY_LEN + MASTER_SALT_LEN);
105 bytes.extend_from_slice(&self.key);
106 bytes.extend_from_slice(&self.salt);
107 base64::engine::general_purpose::STANDARD.encode(bytes)
108 }
109
110 pub fn from_inline(inline: &str) -> Option<Self> {
114 let inline = inline.strip_prefix("inline:").unwrap_or(inline);
115 let mut parts = inline.split('|');
116 let key = parts.next()?;
117 for part in parts {
118 if part.contains(':') {
119 return None; }
121 }
122 let bytes = base64::engine::general_purpose::STANDARD.decode(key).ok()?;
123 Self::from_bytes(&bytes)
124 }
125}
126
127impl fmt::Debug for MasterKey {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 f.write_str("MasterKey([redacted])")
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct CryptoAttribute {
136 pub tag: u32,
138 pub suite: SrtpSuite,
140 pub key: MasterKey,
142}
143
144impl CryptoAttribute {
145 pub fn parse(value: &str) -> Option<Self> {
149 let value = value.strip_prefix("a=crypto:").unwrap_or(value);
150 let mut parts = value.split_whitespace();
151 let tag = parts.next()?.parse().ok()?;
152 let suite = SrtpSuite::from_name(parts.next()?)?;
153 let key_params = parts.next()?;
154 if key_params.contains(';') {
156 return None;
157 }
158 let key = MasterKey::from_inline(key_params)?;
159 if parts.next().is_some() {
162 return None;
163 }
164 Some(Self { tag, suite, key })
165 }
166
167 pub fn to_value(&self) -> String {
169 format!(
170 "{} {} inline:{}",
171 self.tag,
172 self.suite.name(),
173 self.key.to_inline()
174 )
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180#[non_exhaustive]
181pub enum SrtpError {
182 Malformed,
184 AuthenticationFailed,
186 Replayed,
188}
189
190impl fmt::Display for SrtpError {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.write_str(match self {
193 Self::Malformed => "malformed SRTP packet",
194 Self::AuthenticationFailed => "SRTP authentication failed",
195 Self::Replayed => "replayed SRTP packet",
196 })
197 }
198}
199
200impl std::error::Error for SrtpError {}
201
202struct SessionKeys {
204 cipher_key: [u8; MASTER_KEY_LEN],
205 salt: [u8; MASTER_SALT_LEN],
206 auth_key: [u8; AUTH_KEY_LEN],
207}
208
209impl SessionKeys {
210 fn derive(master: &MasterKey) -> Self {
211 Self {
212 cipher_key: derive(master, 0x00),
213 auth_key: derive(master, 0x01),
214 salt: derive(master, 0x02),
215 }
216 }
217}
218
219fn derive<const N: usize>(master: &MasterKey, label: u8) -> [u8; N] {
222 let cipher = Aes128::new(&master.key.into());
223 let blocks: Vec<[u8; 16]> = (0..N.div_ceil(16))
224 .map(|i| {
225 let counter: [u8; 16] = std::array::from_fn(|j| match j {
226 7 => master.salt[j] ^ label,
227 j if j < MASTER_SALT_LEN => master.salt[j],
228 14 => (i >> 8) as u8,
229 _ => i as u8,
230 });
231 let mut block = GenericArray::from(counter);
232 cipher.encrypt_block(&mut block);
233 block.into()
234 })
235 .collect();
236 std::array::from_fn(|k| blocks[k / 16][k % 16])
237}
238
239#[derive(Default)]
241struct Inbound {
242 roc: u32,
243 highest_seq: u16,
244 newest: u64,
246 seen: u64,
247}
248
249#[derive(Default)]
251struct Outbound {
252 roc: u32,
253 last_seq: Option<u16>,
254}
255
256pub struct SrtpSession {
259 suite: SrtpSuite,
260 keys: SessionKeys,
261 cipher: Aes128,
262 outbound: HashMap<u32, Outbound>,
263 inbound: HashMap<u32, Inbound>,
264}
265
266impl fmt::Debug for SrtpSession {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 f.debug_struct("SrtpSession")
269 .field("suite", &self.suite)
270 .finish_non_exhaustive()
271 }
272}
273
274impl SrtpSession {
275 pub fn new(suite: SrtpSuite, key: &MasterKey) -> Self {
277 let keys = SessionKeys::derive(key);
278 let cipher = Aes128::new(&keys.cipher_key.into());
279 Self {
280 suite,
281 keys,
282 cipher,
283 outbound: HashMap::new(),
284 inbound: HashMap::new(),
285 }
286 }
287
288 pub fn protect(&mut self, rtp: &[u8]) -> Option<Vec<u8>> {
291 let header = header_len(rtp)?;
292 let (seq, ssrc) = seq_ssrc(rtp);
293 let state = self.outbound.entry(ssrc).or_default();
294 if let Some(last) = state.last_seq
295 && seq < last
296 && last - seq > 0x8000
297 {
298 state.roc = state.roc.wrapping_add(1);
299 }
300 state.last_seq = Some(seq);
301 let roc = state.roc;
302 let index = (u64::from(roc) << 16) | u64::from(seq);
303
304 let mut packet = rtp.to_vec();
305 self.keystream(ssrc, index, &mut packet[header..]);
306 let tag = self.tag(&packet, roc);
307 packet.extend_from_slice(&tag[..self.suite.tag_len()]);
308 Some(packet)
309 }
310
311 pub fn unprotect(&mut self, srtp: &[u8]) -> Result<Vec<u8>, SrtpError> {
313 let tag_len = self.suite.tag_len();
314 if srtp.len() < tag_len {
315 return Err(SrtpError::Malformed);
316 }
317 let (authenticated, tag) = srtp.split_at(srtp.len() - tag_len);
318 let header = header_len(authenticated).ok_or(SrtpError::Malformed)?;
319 let (seq, ssrc) = seq_ssrc(authenticated);
320
321 let (roc, index) = match self.inbound.get(&ssrc) {
322 Some(state) => {
323 let roc = estimate_roc(state.roc, state.highest_seq, seq);
324 let index = (u64::from(roc) << 16) | u64::from(seq);
325 if is_replay(state, index) {
326 return Err(SrtpError::Replayed);
327 }
328 (roc, index)
329 }
330 None => (0, u64::from(seq)),
331 };
332
333 let mut mac =
334 <HmacSha1 as Mac>::new_from_slice(&self.keys.auth_key).expect("any key length");
335 mac.update(authenticated);
336 mac.update(&roc.to_be_bytes());
337 mac.verify_truncated_left(tag)
338 .map_err(|_| SrtpError::AuthenticationFailed)?;
339
340 let mut packet = authenticated.to_vec();
341 self.keystream(ssrc, index, &mut packet[header..]);
342 self.accept(ssrc, roc, seq, index);
343 Ok(packet)
344 }
345
346 fn accept(&mut self, ssrc: u32, roc: u32, seq: u16, index: u64) {
349 let state = self.inbound.entry(ssrc).or_insert_with(|| Inbound {
350 roc,
351 highest_seq: seq,
352 newest: index,
353 seen: 0,
354 });
355 if index > state.newest {
356 let shift = index - state.newest;
357 state.seen = if shift >= REPLAY_WINDOW {
358 0
359 } else {
360 state.seen << shift
361 };
362 state.newest = index;
363 state.roc = roc;
364 state.highest_seq = seq;
365 }
366 let behind = state.newest - index;
367 state.seen |= 1 << behind;
368 }
369
370 fn keystream(&self, ssrc: u32, index: u64, data: &mut [u8]) {
372 let mut counter = [0u8; 16];
373 counter[..MASTER_SALT_LEN].copy_from_slice(&self.keys.salt);
374 for (i, b) in ssrc.to_be_bytes().iter().enumerate() {
375 counter[4 + i] ^= b;
376 }
377 for (i, b) in index.to_be_bytes()[2..].iter().enumerate() {
378 counter[8 + i] ^= b;
379 }
380 for chunk in data.chunks_mut(16) {
381 let mut block = GenericArray::from(counter);
382 self.cipher.encrypt_block(&mut block);
383 for (d, k) in chunk.iter_mut().zip(block.iter()) {
384 *d ^= k;
385 }
386 let next = u16::from_be_bytes([counter[14], counter[15]]).wrapping_add(1);
387 counter[14..].copy_from_slice(&next.to_be_bytes());
388 }
389 }
390
391 fn tag(&self, authenticated: &[u8], roc: u32) -> [u8; 20] {
392 let mut mac =
393 <HmacSha1 as Mac>::new_from_slice(&self.keys.auth_key).expect("any key length");
394 mac.update(authenticated);
395 mac.update(&roc.to_be_bytes());
396 mac.finalize().into_bytes().into()
397 }
398}
399
400fn header_len(packet: &[u8]) -> Option<usize> {
402 if packet.len() < 12 || packet[0] >> 6 != 2 {
403 return None;
404 }
405 let mut len = 12 + usize::from(packet[0] & 0x0F) * 4;
406 if packet[0] & 0x10 != 0 {
407 let words = packet.get(len + 2..len + 4)?;
408 len += 4 + usize::from(u16::from_be_bytes([words[0], words[1]])) * 4;
409 }
410 (len <= packet.len()).then_some(len)
411}
412
413fn seq_ssrc(packet: &[u8]) -> (u16, u32) {
414 (
415 u16::from_be_bytes([packet[2], packet[3]]),
416 u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
417 )
418}
419
420fn estimate_roc(roc: u32, highest: u16, seq: u16) -> u32 {
423 if highest < 0x8000 {
424 if seq > highest && seq - highest > 0x8000 {
425 roc.wrapping_sub(1)
426 } else {
427 roc
428 }
429 } else if highest - 0x8000 > seq {
430 roc.wrapping_add(1)
431 } else {
432 roc
433 }
434}
435
436fn is_replay(state: &Inbound, index: u64) -> bool {
437 if index > state.newest {
438 return false;
439 }
440 let behind = state.newest - index;
441 behind >= REPLAY_WINDOW || state.seen & (1 << behind) != 0
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 fn hex(s: &str) -> Vec<u8> {
449 let s: String = s.split_whitespace().collect();
450 (0..s.len())
451 .step_by(2)
452 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
453 .collect()
454 }
455
456 fn rfc_master() -> MasterKey {
457 let bytes = hex("E1F97A0D3E018BE0D64FA32C06DE4139 0EC675AD498AFEEBB6960B3AABE6");
458 MasterKey::from_bytes(&bytes).unwrap()
459 }
460
461 #[test]
463 fn key_derivation_matches_rfc_3711() {
464 let keys = SessionKeys::derive(&rfc_master());
465 assert_eq!(
466 keys.cipher_key.to_vec(),
467 hex("C61E7A93744F39EE10734AFE3FF7A087")
468 );
469 assert_eq!(keys.salt.to_vec(), hex("30CBBC08863D8C85D49DB34A9AE1"));
470 assert_eq!(
471 keys.auth_key.to_vec(),
472 hex("CEBE321F6FF7716B6FD4AB49AF256A156D38BAA4")
473 );
474 }
475
476 #[test]
478 fn keystream_matches_rfc_3711() {
479 let key: [u8; 16] = hex("2B7E151628AED2A6ABF7158809CF4F3C").try_into().unwrap();
480 let salt: [u8; 14] = hex("F0F1F2F3F4F5F6F7F8F9FAFBFCFD").try_into().unwrap();
481 let session = SrtpSession {
482 suite: SrtpSuite::AesCm128HmacSha1_80,
483 keys: SessionKeys {
484 cipher_key: key,
485 salt,
486 auth_key: [0; AUTH_KEY_LEN],
487 },
488 cipher: Aes128::new(&key.into()),
489 outbound: HashMap::new(),
490 inbound: HashMap::new(),
491 };
492 let mut stream = [0u8; 48];
493 session.keystream(0, 0, &mut stream);
494 assert_eq!(
495 stream.to_vec(),
496 hex("E03EAD0935C95E80E166B16DD92B4EB4 \
497 D23513162B02D0F72A43A2FE4A5F97AB \
498 41E95B3BB0A2E8DD477901E4FCA894C0")
499 );
500 }
501
502 #[test]
505 fn protects_the_reference_packet() {
506 let plain = hex("800f1234 decafbad cafebabe abababab abababab abababab abababab");
507 let expected = hex(
508 "800f1234 decafbad cafebabe 4e55dc4c e79978d8 8ca4d215 949d2402 \
509 b78d6acc 99ea179b 8dbb",
510 );
511 let mut sender = SrtpSession::new(SrtpSuite::AesCm128HmacSha1_80, &rfc_master());
512 assert_eq!(sender.protect(&plain).unwrap(), expected);
513
514 let mut receiver = SrtpSession::new(SrtpSuite::AesCm128HmacSha1_80, &rfc_master());
515 assert_eq!(receiver.unprotect(&expected).unwrap(), plain);
516 }
517
518 fn rtp(seq: u16, payload: &[u8]) -> Vec<u8> {
519 let mut packet = vec![0x80, 0x00];
520 packet.extend_from_slice(&seq.to_be_bytes());
521 packet.extend_from_slice(&[0, 0, 0, 160]);
522 packet.extend_from_slice(&0x1234_5678u32.to_be_bytes());
523 packet.extend_from_slice(payload);
524 packet
525 }
526
527 #[test]
528 fn a_tampered_or_replayed_packet_is_refused() {
529 let key = MasterKey::generate().unwrap();
530 for suite in [
531 SrtpSuite::AesCm128HmacSha1_80,
532 SrtpSuite::AesCm128HmacSha1_32,
533 ] {
534 let mut tx = SrtpSession::new(suite, &key);
535 let mut rx = SrtpSession::new(suite, &key);
536 let packet = tx.protect(&rtp(7, b"hello")).unwrap();
537 assert_eq!(packet.len(), 12 + 5 + suite.tag_len());
538 assert_ne!(&packet[12..17], b"hello", "the payload is encrypted");
539
540 let mut tampered = packet.clone();
541 tampered[13] ^= 1;
542 assert_eq!(
543 rx.unprotect(&tampered),
544 Err(SrtpError::AuthenticationFailed)
545 );
546
547 assert_eq!(rx.unprotect(&packet).unwrap(), rtp(7, b"hello"));
548 assert_eq!(rx.unprotect(&packet), Err(SrtpError::Replayed));
549
550 let other =
551 SrtpSession::new(suite, &MasterKey::generate().unwrap()).protect(&rtp(8, b"x"));
552 assert_eq!(
553 rx.unprotect(&other.unwrap()),
554 Err(SrtpError::AuthenticationFailed),
555 "a packet under another key"
556 );
557 }
558 }
559
560 #[test]
561 fn the_rollover_counter_follows_a_sequence_wrap() {
562 let key = MasterKey::generate().unwrap();
563 let suite = SrtpSuite::AesCm128HmacSha1_80;
564 let mut tx = SrtpSession::new(suite, &key);
565 let mut rx = SrtpSession::new(suite, &key);
566 let mut seq: u16 = 65_530;
567 for n in 0..20u8 {
568 let packet = tx.protect(&rtp(seq, &[n; 4])).unwrap();
569 assert_eq!(
570 rx.unprotect(&packet).unwrap(),
571 rtp(seq, &[n; 4]),
572 "seq {seq}"
573 );
574 seq = seq.wrapping_add(1);
575 }
576 assert_eq!(tx.outbound[&0x1234_5678].roc, 1);
577 assert_eq!(rx.inbound[&0x1234_5678].roc, 1);
578
579 let mut tx = SrtpSession::new(suite, &key);
582 let mut rx = SrtpSession::new(suite, &key);
583 let early = tx.protect(&rtp(65_534, b"a")).unwrap();
584 let late = tx.protect(&rtp(65_535, b"b")).unwrap();
585 let wrapped = tx.protect(&rtp(0, b"c")).unwrap();
586 assert!(rx.unprotect(&early).is_ok());
587 assert!(rx.unprotect(&wrapped).is_ok());
588 assert_eq!(rx.unprotect(&late).unwrap(), rtp(65_535, b"b"));
589 assert_eq!(rx.unprotect(&late), Err(SrtpError::Replayed));
590 }
591
592 #[test]
593 fn a_crypto_line_round_trips() {
594 let line = "1 AES_CM_128_HMAC_SHA1_80 inline:4fl6DT4Bi+DWT6MsBt5BOQ7Gda1Jiv7rtpYLOqvm|2^20";
595 let attribute = CryptoAttribute::parse(line).unwrap();
596 assert_eq!(attribute.tag, 1);
597 assert_eq!(attribute.suite, SrtpSuite::AesCm128HmacSha1_80);
598 assert_eq!(attribute.key, rfc_master());
599 assert_eq!(
600 CryptoAttribute::parse(&attribute.to_value()).unwrap(),
601 attribute
602 );
603 assert!(
604 !format!("{attribute:?}").contains("4fl6DT4B"),
605 "keys never print"
606 );
607
608 assert_eq!(
609 CryptoAttribute::parse(
610 "1 F8_128_HMAC_SHA1_80 inline:4fl6DT4Bi+DWT6MsBt5BOQ7Gda1Jiv7rtpYLOqvm"
611 ),
612 None
613 );
614 assert_eq!(
615 CryptoAttribute::parse(
616 "1 AES_CM_128_HMAC_SHA1_80 inline:4fl6DT4Bi+DWT6MsBt5BOQ7Gda1Jiv7rtpYLOqvm|2^20|1:4"
617 ),
618 None,
619 "an MKI"
620 );
621 assert_eq!(
622 CryptoAttribute::parse(
623 "1 AES_CM_128_HMAC_SHA1_80 inline:4fl6DT4Bi+DWT6MsBt5BOQ7Gda1Jiv7rtpYLOqvm KDR=1"
624 ),
625 None,
626 "a session parameter"
627 );
628 }
629}