Skip to main content

sipx_rtp/
srtp.rs

1//! SRTP (RFC 3711): the default transform, AES-128 counter mode with HMAC-SHA1.
2//!
3//! What SRTP protects and what it does not is worth being exact about, because the gap is where
4//! people are surprised. It encrypts the *payload* and authenticates the *whole packet*
5//! including the header. So the sequence number, timestamp and SSRC travel in the clear and
6//! cannot be altered; the audio travels encrypted. That is deliberate — a relay has to read the
7//! header to do its job.
8//!
9//! Three things here are easy to get wrong and each has a test against the RFC's own published
10//! numbers rather than against this implementation's opinion of them:
11//!
12//! **Key derivation** (§4.3.1) turns one master key into six session keys through AES counter
13//! mode. Getting the label or the salt alignment wrong produces keys that are perfectly
14//! self-consistent — two endpoints running the same wrong code interoperate happily and neither
15//! interoperates with anything else.
16//!
17//! **The packet index** (§3.3.1) is 48 bits: a 32-bit rollover counter above the 16-bit sequence
18//! number. It is not sent. Both ends infer it, and an implementation that guesses differently
19//! decrypts to noise at the first wrap — twenty minutes into a call, at speech packet rates.
20//!
21//! **Replay** (§3.3.2) is rejected by a sliding window rather than by remembering everything.
22//! Without it, a captured packet can be replayed into a call for as long as the key lives.
23
24use aes::Aes128;
25use aes::cipher::{KeyIvInit, StreamCipher};
26use hmac::{Hmac, Mac};
27use sha1::Sha1;
28use subtle::ConstantTimeEq;
29
30type Aes128Ctr = ctr::Ctr128BE<Aes128>;
31type HmacSha1 = Hmac<Sha1>;
32
33/// The master key length of the default transform.
34pub const MASTER_KEY_LEN: usize = 16;
35/// The master salt length of the default transform.
36pub const MASTER_SALT_LEN: usize = 14;
37/// The authentication tag length of `AES_CM_128_HMAC_SHA1_80`, in octets.
38pub const TAG_LEN: usize = 10;
39
40const SESSION_KEY_LEN: usize = 16;
41const SESSION_SALT_LEN: usize = 14;
42/// `n_a`, the session authentication key length: 160 bits (RFC 3711 §5.2, §8.2).
43///
44/// §4.3.1 derives `n = n_a` octets under label 0x01 and fixes no length of its own; §5.2 fixes
45/// `n_a` at 160 bits for the pre-defined HMAC-SHA1 transform, and §8.2's table lists it as both
46/// mandatory-to-support and the default. §B.3's worked example derives **94** octets because that
47/// appendix posits an authentication function needing 94, in order to walk the PRF through six AES
48/// blocks — a property of the example, not of the transform. HMAC accepts a key of any length,
49/// which is what lets the two be confused without any error to say so.
50const SESSION_AUTH_LEN: usize = 20;
51
52/// Which session key is being derived (RFC 3711 §4.3.1).
53#[derive(Debug, Clone, Copy)]
54enum Label {
55    RtpEncryption = 0x00,
56    RtpAuthentication = 0x01,
57    RtpSalt = 0x02,
58    RtcpEncryption = 0x03,
59    RtcpAuthentication = 0x04,
60    RtcpSalt = 0x05,
61}
62
63/// What can go wrong protecting or unprotecting a packet.
64#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
65#[non_exhaustive]
66pub enum SrtpError {
67    /// The key or salt was not the length the transform requires.
68    #[error("{what} must be {expected} octets, not {actual}")]
69    KeyLength {
70        /// Which one.
71        what: &'static str,
72        /// How long it should be.
73        expected: usize,
74        /// How long it was.
75        actual: usize,
76    },
77    /// Too short to be a packet of this kind at all.
78    #[error("packet is {0} octets; too short to be authenticated")]
79    TooShort(usize),
80    /// The authentication tag did not match.
81    ///
82    /// Deliberately says nothing about *why*. A caller that could tell "wrong key" from
83    /// "altered packet" would be an oracle.
84    #[error("authentication failed")]
85    NotAuthentic,
86    /// The packet has been seen before, or is too old to judge (RFC 3711 §3.3.2).
87    #[error("replayed or too old: sequence {0}")]
88    Replayed(u16),
89    /// The SRTCP packet has been seen before, or its 31-bit index is too old to judge.
90    #[error("replayed or too old SRTCP index {0}")]
91    ReplayedRtcp(u32),
92}
93
94/// The six session keys one master key produces.
95#[derive(Clone)]
96struct Session {
97    rtp_key: [u8; SESSION_KEY_LEN],
98    rtp_salt: [u8; SESSION_SALT_LEN],
99    rtp_auth: [u8; SESSION_AUTH_LEN],
100    rtcp_key: [u8; SESSION_KEY_LEN],
101    rtcp_salt: [u8; SESSION_SALT_LEN],
102    rtcp_auth: [u8; SESSION_AUTH_LEN],
103}
104
105impl std::fmt::Debug for Session {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        // Keys. Printing them would put them in whatever log the caller writes.
108        f.write_str("Session { .. }")
109    }
110}
111
112/// Derive `out.len()` octets of session key material (RFC 3711 §4.3.1).
113///
114/// The input block is the master salt with the label exclusive-ored into octet 7 and
115/// `index DIV kdr` into octets 8..14, shifted left by two octets. sipx uses a key derivation
116/// rate of zero — one derivation per master key, which is what `index DIV kdr` being zero means
117/// — because rekeying mid-stream buys nothing until there is a way to signal it.
118fn derive(
119    master_key: &[u8; MASTER_KEY_LEN],
120    master_salt: &[u8; MASTER_SALT_LEN],
121    label: Label,
122    out: &mut [u8],
123) {
124    let mut iv = [0u8; 16];
125    iv[..MASTER_SALT_LEN].copy_from_slice(master_salt);
126    iv[7] ^= label as u8;
127    // Octets 8..14 would carry `index DIV kdr`; with a rate of zero it is six zero octets, and
128    // exclusive-oring zero changes nothing. Written out so the alignment is visible rather than
129    // implied by its absence.
130    iv[14] = 0;
131    iv[15] = 0;
132
133    out.fill(0);
134    let mut cipher = Aes128Ctr::new(master_key.into(), (&iv).into());
135    cipher.apply_keystream(out);
136}
137
138/// One direction of one SRTP stream.
139///
140/// Directional on purpose: RFC 3711 keys each direction separately, and a context used for both
141/// would have two senders sharing one replay window and one rollover counter.
142#[derive(Debug)]
143pub struct Context {
144    session: Session,
145    /// The rollover counter — the high 32 bits of the 48-bit packet index (§3.3.1).
146    roc: u32,
147    /// The highest sequence number seen, for inferring the rollover.
148    highest_seq: Option<u16>,
149    /// The replay window, most recent packet at bit 0 (§3.3.2).
150    replay: u64,
151    /// The SRTCP index this side sends, 31 bits (§3.4).
152    rtcp_index: u32,
153    /// The highest authenticated SRTCP index received, separate from the SRTP sequence/ROC (§3.4).
154    highest_rtcp_index: Option<u32>,
155    /// The SRTCP replay window, most recent authenticated index at bit 0 (§3.4, §3.3.2).
156    rtcp_replay: u64,
157}
158
159impl Context {
160    /// A context from a master key and salt.
161    pub fn new(master_key: &[u8], master_salt: &[u8]) -> Result<Self, SrtpError> {
162        let key: &[u8; MASTER_KEY_LEN] =
163            master_key.try_into().map_err(|_| SrtpError::KeyLength {
164                what: "master key",
165                expected: MASTER_KEY_LEN,
166                actual: master_key.len(),
167            })?;
168        let salt: &[u8; MASTER_SALT_LEN] =
169            master_salt.try_into().map_err(|_| SrtpError::KeyLength {
170                what: "master salt",
171                expected: MASTER_SALT_LEN,
172                actual: master_salt.len(),
173            })?;
174
175        let mut session = Session {
176            rtp_key: [0; SESSION_KEY_LEN],
177            rtp_salt: [0; SESSION_SALT_LEN],
178            rtp_auth: [0; SESSION_AUTH_LEN],
179            rtcp_key: [0; SESSION_KEY_LEN],
180            rtcp_salt: [0; SESSION_SALT_LEN],
181            rtcp_auth: [0; SESSION_AUTH_LEN],
182        };
183        derive(key, salt, Label::RtpEncryption, &mut session.rtp_key);
184        derive(key, salt, Label::RtpSalt, &mut session.rtp_salt);
185        derive(key, salt, Label::RtpAuthentication, &mut session.rtp_auth);
186        derive(key, salt, Label::RtcpEncryption, &mut session.rtcp_key);
187        derive(key, salt, Label::RtcpSalt, &mut session.rtcp_salt);
188        derive(key, salt, Label::RtcpAuthentication, &mut session.rtcp_auth);
189
190        Ok(Self {
191            session,
192            roc: 0,
193            highest_seq: None,
194            replay: 0,
195            rtcp_index: 0,
196            highest_rtcp_index: None,
197            rtcp_replay: 0,
198        })
199    }
200
201    /// Encrypt and authenticate an RTP packet in place, returning it with the tag appended.
202    ///
203    /// `packet` is a complete serialized RTP packet. The header is left readable — a relay has
204    /// to see the sequence number and SSRC to do its job — and authenticated, so it cannot be
205    /// altered without detection.
206    pub fn protect(&mut self, packet: &[u8]) -> Result<Vec<u8>, SrtpError> {
207        let header_len = rtp_header_len(packet).ok_or(SrtpError::TooShort(packet.len()))?;
208        let (sequence, ssrc) =
209            sequence_and_ssrc(packet).ok_or(SrtpError::TooShort(packet.len()))?;
210
211        // The sender's index simply follows its own sequence numbers.
212        // A sender knows its own order, so a large step backwards is a wrap rather than a guess.
213        let roc = match self.highest_seq {
214            Some(previous) if i32::from(previous) - i32::from(sequence) > 32_768 => {
215                self.roc = self.roc.wrapping_add(1);
216                self.roc
217            }
218            _ => self.roc,
219        };
220        self.highest_seq = Some(sequence);
221
222        let mut out = packet.to_vec();
223        let (_, payload) = out.split_at_mut(header_len);
224        keystream(
225            &self.session.rtp_key,
226            &self.session.rtp_salt,
227            ssrc,
228            index_of(roc, sequence),
229        )
230        .apply_keystream(payload);
231
232        // The tag covers the whole packet *and* the rollover counter, which is not transmitted.
233        // Without the ROC in the tag, a packet from before a wrap could be replayed after it.
234        let tag = authenticate(&self.session.rtp_auth, &out, Some(roc));
235        out.extend_from_slice(&tag);
236        Ok(out)
237    }
238
239    /// Authenticate and decrypt an RTP packet, returning the plaintext packet.
240    ///
241    /// Authentication happens **before** decryption and before the replay window is updated: a
242    /// packet that fails it never touches this context's state, which is what stops an attacker
243    /// advancing the window with forgeries.
244    pub fn unprotect(&mut self, packet: &[u8]) -> Result<Vec<u8>, SrtpError> {
245        if packet.len() < TAG_LEN {
246            return Err(SrtpError::TooShort(packet.len()));
247        }
248        let (body, tag) = packet.split_at(packet.len() - TAG_LEN);
249        let header_len = rtp_header_len(body).ok_or(SrtpError::TooShort(body.len()))?;
250        let (sequence, ssrc) = sequence_and_ssrc(body).ok_or(SrtpError::TooShort(body.len()))?;
251
252        let roc = self.guess_roc(sequence);
253        let expected = authenticate(&self.session.rtp_auth, body, Some(roc));
254        if expected.ct_eq(tag).unwrap_u8() != 1 {
255            return Err(SrtpError::NotAuthentic);
256        }
257
258        // Only now, with the packet proven genuine, is replay considered.
259        self.check_replay(roc, sequence)?;
260
261        let mut out = body.to_vec();
262        let (_, payload) = out.split_at_mut(header_len);
263        keystream(
264            &self.session.rtp_key,
265            &self.session.rtp_salt,
266            ssrc,
267            index_of(roc, sequence),
268        )
269        .apply_keystream(payload);
270
271        self.accept(roc, sequence);
272        Ok(out)
273    }
274
275    /// Encrypt and authenticate an RTCP compound packet (RFC 3711 §3.4).
276    pub fn protect_rtcp(&mut self, packet: &[u8]) -> Result<Vec<u8>, SrtpError> {
277        // The first eight octets — header and sender SSRC — stay readable, as with RTP.
278        const RTCP_HEADER_LEN: usize = 8;
279        if packet.len() < RTCP_HEADER_LEN {
280            return Err(SrtpError::TooShort(packet.len()));
281        }
282        let ssrc = u32::from_be_bytes(
283            packet
284                .get(4..8)
285                .and_then(|s| s.try_into().ok())
286                .ok_or(SrtpError::TooShort(packet.len()))?,
287        );
288        // §3.4: the index "MUST be set to zero before the first SRTCP packet is sent, and MUST be
289        // incremented by one, modulo 2^31, *after* each SRTCP packet is sent". Read then advance,
290        // so the first packet carries zero and no index is ever skipped.
291        let index = self.rtcp_index;
292        self.rtcp_index = self.rtcp_index.wrapping_add(1) & 0x7FFF_FFFF;
293
294        let mut out = packet.to_vec();
295        let (_, payload) = out.split_at_mut(RTCP_HEADER_LEN);
296        keystream(
297            &self.session.rtcp_key,
298            &self.session.rtcp_salt,
299            ssrc,
300            u64::from(index),
301        )
302        .apply_keystream(payload);
303
304        // The trailer carries the encryption flag and the index in the clear; the tag covers it.
305        out.extend_from_slice(&(index | 0x8000_0000).to_be_bytes());
306        let tag = authenticate(&self.session.rtcp_auth, &out, None);
307        out.extend_from_slice(&tag);
308        Ok(out)
309    }
310
311    /// Authenticate and decrypt an RTCP compound packet.
312    pub fn unprotect_rtcp(&mut self, packet: &[u8]) -> Result<Vec<u8>, SrtpError> {
313        const RTCP_HEADER_LEN: usize = 8;
314        const TRAILER_LEN: usize = 4;
315        if packet.len() < RTCP_HEADER_LEN + TRAILER_LEN + TAG_LEN {
316            return Err(SrtpError::TooShort(packet.len()));
317        }
318        let (body, tag) = packet.split_at(packet.len() - TAG_LEN);
319        let expected = authenticate(&self.session.rtcp_auth, body, None);
320        if expected.ct_eq(tag).unwrap_u8() != 1 {
321            return Err(SrtpError::NotAuthentic);
322        }
323
324        let (payload_and_header, trailer) = body.split_at(body.len() - TRAILER_LEN);
325        let trailer = u32::from_be_bytes(
326            trailer
327                .try_into()
328                .map_err(|_| SrtpError::TooShort(packet.len()))?,
329        );
330        let encrypted = trailer & 0x8000_0000 != 0;
331        let index = trailer & 0x7FFF_FFFF;
332        // The explicit SRTCP index has authenticated by this point. Only now may it be compared
333        // with the replay window; a forged high index therefore cannot move trusted state.
334        self.check_rtcp_replay(index)?;
335        let ssrc = u32::from_be_bytes(
336            body.get(4..8)
337                .and_then(|s| s.try_into().ok())
338                .ok_or(SrtpError::TooShort(body.len()))?,
339        );
340
341        let mut out = payload_and_header.to_vec();
342        if encrypted {
343            let (_, payload) = out.split_at_mut(RTCP_HEADER_LEN);
344            keystream(
345                &self.session.rtcp_key,
346                &self.session.rtcp_salt,
347                ssrc,
348                u64::from(index),
349            )
350            .apply_keystream(payload);
351        }
352        self.accept_rtcp(index);
353        Ok(out)
354    }
355
356    /// The rollover counter this sequence number most likely belongs to (RFC 3711 §3.3.1).
357    ///
358    /// The arithmetic is **signed**, and that is the whole subtlety. The RFC writes
359    /// `if (SEQ - s_l > 32768)` over two 16-bit values, and it means ordinary subtraction that
360    /// may go negative — not wrapping `u16` subtraction. Read as wrapping, a packet arriving one
361    /// place out of order looks 65 535 ahead, is taken for the previous cycle, and fails
362    /// authentication. Every out-of-order packet in a call, silently dropped.
363    fn guess_roc(&self, sequence: u16) -> u32 {
364        let Some(highest) = self.highest_seq else {
365            return self.roc;
366        };
367        let (sequence, highest) = (i32::from(sequence), i32::from(highest));
368
369        if highest < 32_768 {
370            // Near the start of a cycle: a number far *above* us is from the previous one.
371            if sequence - highest > 32_768 {
372                return self.roc.wrapping_sub(1);
373            }
374        } else if highest - 32_768 > sequence {
375            // Near the end of a cycle: a number far *below* us has already wrapped.
376            return self.roc.wrapping_add(1);
377        }
378        self.roc
379    }
380
381    fn check_replay(&self, roc: u32, sequence: u16) -> Result<(), SrtpError> {
382        let Some(highest) = self.highest_seq else {
383            return Ok(());
384        };
385        let incoming = index_of(roc, sequence);
386        let current = index_of(self.roc, highest);
387
388        if incoming > current {
389            return Ok(());
390        }
391        let behind = current - incoming;
392        if behind >= 64 {
393            // Older than the window can judge. Refused rather than accepted: accepting it would
394            // mean a packet captured minutes ago could be replayed for as long as the key lives.
395            return Err(SrtpError::Replayed(sequence));
396        }
397        if self.replay & (1 << behind) != 0 {
398            return Err(SrtpError::Replayed(sequence));
399        }
400        Ok(())
401    }
402
403    fn accept(&mut self, roc: u32, sequence: u16) {
404        let incoming = index_of(roc, sequence);
405        let current = self
406            .highest_seq
407            .map_or(0, |highest| index_of(self.roc, highest));
408
409        if self.highest_seq.is_none() || incoming > current {
410            let advance = if self.highest_seq.is_none() {
411                0
412            } else {
413                incoming - current
414            };
415            self.replay = if advance >= 64 {
416                0
417            } else {
418                self.replay << advance
419            };
420            self.replay |= 1;
421            self.roc = roc;
422            self.highest_seq = Some(sequence);
423        } else {
424            let behind = current - incoming;
425            if behind < 64 {
426                self.replay |= 1 << behind;
427            }
428        }
429    }
430
431    fn check_rtcp_replay(&self, index: u32) -> Result<(), SrtpError> {
432        let Some(highest) = self.highest_rtcp_index else {
433            return Ok(());
434        };
435        if srtcp_forward_distance(highest, index).is_some() {
436            return Ok(());
437        }
438        let behind = highest.wrapping_sub(index) & SRTCP_INDEX_MASK;
439        if behind >= 64 || self.rtcp_replay & (1u64 << behind) != 0 {
440            return Err(SrtpError::ReplayedRtcp(index));
441        }
442        Ok(())
443    }
444
445    fn accept_rtcp(&mut self, index: u32) {
446        let Some(highest) = self.highest_rtcp_index else {
447            self.highest_rtcp_index = Some(index);
448            self.rtcp_replay = 1;
449            return;
450        };
451        if let Some(advance) = srtcp_forward_distance(highest, index) {
452            self.rtcp_replay = if advance >= 64 {
453                0
454            } else {
455                self.rtcp_replay << advance
456            };
457            self.rtcp_replay |= 1;
458            self.highest_rtcp_index = Some(index);
459        } else {
460            let behind = highest.wrapping_sub(index) & SRTCP_INDEX_MASK;
461            if behind < 64 {
462                self.rtcp_replay |= 1u64 << behind;
463            }
464        }
465    }
466}
467
468const SRTCP_INDEX_MASK: u32 = 0x7FFF_FFFF;
469const SRTCP_INDEX_HALF_RANGE: u32 = 0x4000_0000;
470
471/// The forward distance in the 31-bit SRTCP index space, or `None` when `incoming` is not newer.
472///
473/// RFC 3711 limits one key to the index space, but treating the modulo boundary normally keeps the
474/// held replay window correct at the last packet while the caller arranges rekeying. Exactly half
475/// the space is ambiguous and is deliberately not considered newer.
476fn srtcp_forward_distance(current: u32, incoming: u32) -> Option<u32> {
477    let distance = incoming.wrapping_sub(current) & SRTCP_INDEX_MASK;
478    (distance != 0 && distance < SRTCP_INDEX_HALF_RANGE).then_some(distance)
479}
480
481/// The 48-bit packet index: the rollover counter above the sequence number.
482fn index_of(roc: u32, sequence: u16) -> u64 {
483    (u64::from(roc) << 16) | u64::from(sequence)
484}
485
486/// The keystream generator for one packet (RFC 3711 §4.1.1).
487///
488/// `IV = (salt * 2^16) XOR (SSRC * 2^64) XOR (index * 2^16)`. Every term is shifted left by at
489/// least two octets, so the low 16 bits of the IV are always zero — which is why a plain
490/// 128-bit counter is correct here: it cannot carry into the rest of the block within any packet
491/// short enough to exist.
492fn keystream(
493    key: &[u8; SESSION_KEY_LEN],
494    salt: &[u8; SESSION_SALT_LEN],
495    ssrc: u32,
496    index: u64,
497) -> Aes128Ctr {
498    let mut iv = [0u8; 16];
499    iv[..SESSION_SALT_LEN].copy_from_slice(salt);
500
501    for (slot, byte) in iv.iter_mut().skip(4).zip(ssrc.to_be_bytes()) {
502        *slot ^= byte;
503    }
504    // The low 48 bits of the index, which is what a packet index is.
505    for (slot, byte) in iv
506        .iter_mut()
507        .skip(8)
508        .zip(index.to_be_bytes().into_iter().skip(2))
509    {
510        *slot ^= byte;
511    }
512    Aes128Ctr::new(key.into(), (&iv).into())
513}
514
515/// HMAC-SHA1 over the packet, truncated to `n_tag` = 80 bits (RFC 3711 §4.2.1).
516///
517/// `M` is the authenticated portion of the packet, followed by the rollover counter for SRTP and
518/// by nothing for SRTCP (§4.2). The key is taken as a slice rather than as `[u8; SESSION_AUTH_LEN]`
519/// so a test can hand it one the RFC published rather than one this module derived.
520fn authenticate(key: &[u8], data: &[u8], roc: Option<u32>) -> [u8; TAG_LEN] {
521    let mut mac = <HmacSha1 as Mac>::new_from_slice(key)
522        .unwrap_or_else(|_| unreachable!("HMAC accepts a key of any length"));
523    mac.update(data);
524    if let Some(roc) = roc {
525        mac.update(&roc.to_be_bytes());
526    }
527    let full = mac.finalize().into_bytes();
528    let mut tag = [0u8; TAG_LEN];
529    // SHA-1 is 20 octets and the tag is 10, so the slice is always there.
530    tag.copy_from_slice(full.get(..TAG_LEN).unwrap_or(&[0u8; TAG_LEN]));
531    tag
532}
533
534/// The sequence number and SSRC of an RTP packet.
535///
536/// Read fallibly rather than indexed. This function is handed whatever arrived on a UDP socket,
537/// and a length check three lines above is not a guarantee a later reader will preserve.
538fn sequence_and_ssrc(packet: &[u8]) -> Option<(u16, u32)> {
539    let sequence = u16::from_be_bytes(packet.get(2..4)?.try_into().ok()?);
540    let ssrc = u32::from_be_bytes(packet.get(8..12)?.try_into().ok()?);
541    Some((sequence, ssrc))
542}
543
544/// How long the RTP header is, including CSRCs and any extension.
545///
546/// `None` when the buffer is too short to hold what it claims, which is the case a decoder that
547/// trusts the length field turns into a panic.
548fn rtp_header_len(packet: &[u8]) -> Option<usize> {
549    let first = *packet.first()?;
550    if packet.len() < 12 {
551        return None;
552    }
553    let csrc_count = usize::from(first & 0x0F);
554    let mut len = 12 + csrc_count * 4;
555    if first & 0x10 != 0 {
556        // An extension: four octets of header, then a length in 32-bit words.
557        let words = usize::from(u16::from_be_bytes([
558            *packet.get(len + 2)?,
559            *packet.get(len + 3)?,
560        ]));
561        len += 4 + words * 4;
562    }
563    (len <= packet.len()).then_some(len)
564}
565
566#[cfg(test)]
567#[allow(
568    clippy::unwrap_used,
569    clippy::expect_used,
570    clippy::panic,
571    clippy::indexing_slicing
572)]
573mod tests {
574    use super::*;
575
576    fn hex(text: &str) -> Vec<u8> {
577        (0..text.len())
578            .step_by(2)
579            .map(|i| u8::from_str_radix(&text[i..i + 2], 16).expect("hex"))
580            .collect()
581    }
582
583    /// RFC 3711 §B.3, checked against the numbers the RFC publishes rather than against this
584    /// implementation's own arithmetic.
585    ///
586    /// This is the test that matters most in the file. A key derivation that is wrong but
587    /// self-consistent produces two endpoints that interoperate perfectly with each other and
588    /// with nothing else in the world — and every round-trip test in this module would pass.
589    ///
590    /// It exercises the PRF at the lengths §B.3 uses, which for the authentication label is 94
591    /// octets — six AES blocks, enough to catch a counter that does not advance. That is a property
592    /// of the appendix and **not** the transform's `n_a`; how many of these octets the default
593    /// transform actually keys with is
594    /// `the_session_authentication_key_is_the_160_bits_the_rfc_fixes`.
595    #[test]
596    fn key_derivation_matches_the_rfc() {
597        let master_key: [u8; 16] = hex("E1F97A0D3E018BE0D64FA32C06DE4139").try_into().unwrap();
598        let master_salt: [u8; 14] = hex("0EC675AD498AFEEBB6960B3AABE6").try_into().unwrap();
599
600        let mut cipher_key = [0u8; 16];
601        derive(
602            &master_key,
603            &master_salt,
604            Label::RtpEncryption,
605            &mut cipher_key,
606        );
607        assert_eq!(cipher_key.to_vec(), hex("C61E7A93744F39EE10734AFE3FF7A087"));
608
609        let mut cipher_salt = [0u8; 14];
610        derive(&master_key, &master_salt, Label::RtpSalt, &mut cipher_salt);
611        assert_eq!(cipher_salt.to_vec(), hex("30CBBC08863D8C85D49DB34A9AE1"));
612
613        let mut auth_key = [0u8; 94];
614        derive(
615            &master_key,
616            &master_salt,
617            Label::RtpAuthentication,
618            &mut auth_key,
619        );
620        assert_eq!(
621            auth_key.to_vec(),
622            hex("CEBE321F6FF7716B6FD4AB49AF256A15\
623                 6D38BAA48F0A0ACF3C34E2359E6CDBCE\
624                 E049646C43D9327AD175578EF7227098\
625                 6371C10C9A369AC2F94A8C5FBCDDDC25\
626                 6D6E919A48B610EF17C2041E47403576\
627                 6B68642C59BBFC2F34DB60DBDFB2")
628        );
629    }
630
631    /// The session authentication key is 160 bits, not §B.3's 94 octets.
632    ///
633    /// RFC 3711 §5.2: "The default session authentication key-length (`n_a`) SHALL be 160 bits", and
634    /// §8.2's table repeats it. §4.3.1 derives `n = n_a` octets under label 0x01 — it does not fix
635    /// a length of its own. §B.3 walks through a **94**-octet derivation because that appendix
636    /// posits "an authentication function which requires a 94-octet session authentication key" to
637    /// exercise six AES blocks of the PRF; 94 is a property of the worked example, not of the
638    /// default transform.
639    ///
640    /// Reading it the other way produces a stack whose HMAC key is a different length from every
641    /// conformant peer's, so every packet fails authentication in both directions — and every
642    /// round-trip test still passes, because both ends are wrong the same way.
643    #[test]
644    fn the_session_authentication_key_is_the_160_bits_the_rfc_fixes() {
645        let context = Context::new(
646            &hex("E1F97A0D3E018BE0D64FA32C06DE4139"),
647            &hex("0EC675AD498AFEEBB6960B3AABE6"),
648        )
649        .expect("a context");
650
651        assert_eq!(
652            context.session.rtp_auth.len(),
653            20,
654            "n_a SHALL be 160 bits (RFC 3711 §5.2, §8.2)"
655        );
656        // The first 160 bits of §B.3's own derived block, which is what `n = n_a` selects.
657        assert_eq!(
658            context.session.rtp_auth.to_vec(),
659            hex("CEBE321F6FF7716B6FD4AB49AF256A156D38BAA4")
660        );
661        assert_eq!(context.session.rtcp_auth.len(), 20, "and for SRTCP too");
662    }
663
664    /// RFC 3711 §4.2.1's tag, over inputs the RFC publishes and against a value this stack did not
665    /// produce.
666    ///
667    /// `k_a` is §B.3's derived authentication key truncated to `n_a`; `M` is §B.1's published RTP
668    /// header and the ROC is §B.1's published rollover counter. The expected tags are HMAC-SHA1
669    /// (RFC 2104) truncated to `n_tag` = 80 bits, computed with an implementation outside this
670    /// repository — a tag that agrees only with [`authenticate`] proves nothing about either.
671    ///
672    /// Both forms of `M` are pinned, because they differ: §4.2 appends the ROC for SRTP and not for
673    /// SRTCP, whose index travels in the packet instead.
674    #[test]
675    fn the_authentication_tag_is_hmac_sha1_over_the_packet_and_the_roc() {
676        let k_a = hex("CEBE321F6FF7716B6FD4AB49AF256A156D38BAA4");
677        let m = hex("806E5CBA50681DE55C621599");
678
679        assert_eq!(
680            authenticate(&k_a, &m, Some(0xD462_564A)).to_vec(),
681            hex("2E19C5351B7F99278F33"),
682            "SRTP: M = Authenticated Portion || ROC"
683        );
684        assert_eq!(
685            authenticate(&k_a, &m, None).to_vec(),
686            hex("66126DD7550B7E7C90A4"),
687            "SRTCP: M = Authenticated Portion only"
688        );
689    }
690
691    /// RFC 3711 §3.4: "The SRTCP index MUST be set to zero before the first SRTCP packet is sent,
692    /// and MUST be incremented by one, modulo 2^31, **after** each SRTCP packet is sent."
693    ///
694    /// Incrementing first makes the first packet carry 1 and never emits index 0 at all. It is not
695    /// an interoperability failure — the index is explicit in the trailer, so a receiver reads
696    /// whatever arrives — but it is a stated MUST, and the index feeds the SRTCP keystream IV, so
697    /// "which packet used which counter block" is not a free choice.
698    #[test]
699    fn the_first_srtcp_packet_carries_index_zero() {
700        let (mut send, _) = pair();
701        let mut packet = vec![0x80, 201, 0x00, 0x07];
702        packet.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
703        packet.extend_from_slice(b"REPORTBODY-REPORTBODY-RE");
704
705        let first = send.protect_rtcp(&packet).expect("protects");
706        let trailer = trailer_of(&first);
707        assert_eq!(trailer & 0x8000_0000, 0x8000_0000, "the E flag is set");
708        assert_eq!(trailer & 0x7FFF_FFFF, 0, "the first index is zero");
709
710        let second = send.protect_rtcp(&packet).expect("protects");
711        assert_eq!(
712            trailer_of(&second) & 0x7FFF_FFFF,
713            1,
714            "and it increments after each packet, not before"
715        );
716    }
717
718    /// The four trailer octets that precede the authentication tag.
719    fn trailer_of(protected: &[u8]) -> u32 {
720        let end = protected.len() - TAG_LEN;
721        u32::from_be_bytes(protected[end - 4..end].try_into().expect("four octets"))
722    }
723
724    /// RFC 3711 §B.2. The counter block and the keystream it produces, from the RFC.
725    #[test]
726    fn the_keystream_matches_the_rfc() {
727        let key: [u8; 16] = hex("2B7E151628AED2A6ABF7158809CF4F3C").try_into().unwrap();
728        // The RFC gives the offset already shifted; the salt is its first fourteen octets, and
729        // SSRC and index are zero, so the IV is exactly that offset.
730        let salt: [u8; 14] = hex("F0F1F2F3F4F5F6F7F8F9FAFBFCFD").try_into().unwrap();
731
732        let mut out = [0u8; 48];
733        keystream(&key, &salt, 0, 0).apply_keystream(&mut out);
734
735        assert_eq!(out[..16].to_vec(), hex("E03EAD0935C95E80E166B16DD92B4EB4"));
736        assert_eq!(
737            out[16..32].to_vec(),
738            hex("D23513162B02D0F72A43A2FE4A5F97AB")
739        );
740        assert_eq!(out[32..].to_vec(), hex("41E95B3BB0A2E8DD477901E4FCA894C0"));
741    }
742
743    fn rtp(sequence: u16, payload: &[u8]) -> Vec<u8> {
744        let mut packet = vec![0x80, 0x00];
745        packet.extend_from_slice(&sequence.to_be_bytes());
746        packet.extend_from_slice(&(u32::from(sequence) * 160).to_be_bytes());
747        packet.extend_from_slice(&0xDEAD_BEEFu32.to_be_bytes());
748        packet.extend_from_slice(payload);
749        packet
750    }
751
752    fn pair() -> (Context, Context) {
753        let key = [7u8; 16];
754        let salt = [9u8; 14];
755        (
756            Context::new(&key, &salt).expect("a sender"),
757            Context::new(&key, &salt).expect("a receiver"),
758        )
759    }
760
761    #[test]
762    fn a_protected_packet_round_trips() {
763        let (mut send, mut recv) = pair();
764        let plain = rtp(1000, b"the quick brown fox jumps");
765
766        let protected = send.protect(&plain).expect("protects");
767        assert_eq!(protected.len(), plain.len() + TAG_LEN);
768        assert_eq!(recv.unprotect(&protected).expect("unprotects"), plain);
769    }
770
771    /// The header stays readable and the payload does not. That split is the whole design: a
772    /// relay must see the sequence number, and nobody should hear the audio.
773    #[test]
774    fn the_header_is_readable_and_the_payload_is_not() {
775        let (mut send, _) = pair();
776        let plain = rtp(7, b"SECRET AUDIO SAMPLES HERE");
777        let protected = send.protect(&plain).expect("protects");
778
779        assert_eq!(
780            &protected[..12],
781            &plain[..12],
782            "the header travels in the clear"
783        );
784        assert!(
785            !protected.windows(6).any(|w| w == b"SECRET"),
786            "the payload must not appear on the wire"
787        );
788    }
789
790    #[test]
791    fn an_altered_packet_is_refused() {
792        let (mut send, mut recv) = pair();
793        let mut protected = send.protect(&rtp(1, b"hello")).expect("protects");
794
795        // One bit, anywhere.
796        protected[14] ^= 0x01;
797        assert_eq!(recv.unprotect(&protected), Err(SrtpError::NotAuthentic));
798    }
799
800    /// Including the header, which is not encrypted and would otherwise be free to rewrite.
801    #[test]
802    fn an_altered_header_is_refused() {
803        let (mut send, mut recv) = pair();
804        let mut protected = send.protect(&rtp(1, b"hello")).expect("protects");
805
806        protected[3] ^= 0x01; // the sequence number
807        assert_eq!(recv.unprotect(&protected), Err(SrtpError::NotAuthentic));
808    }
809
810    #[test]
811    fn a_packet_from_a_different_key_is_refused() {
812        let (mut send, _) = pair();
813        let mut stranger = Context::new(&[1u8; 16], &[2u8; 14]).expect("a context");
814        let protected = send.protect(&rtp(1, b"hello")).expect("protects");
815        assert_eq!(stranger.unprotect(&protected), Err(SrtpError::NotAuthentic));
816    }
817
818    /// RFC 3711 §3.3.2. Without this a captured packet can be replayed into a call for as long
819    /// as the key lives, and it authenticates perfectly because it is genuine.
820    #[test]
821    fn a_replayed_packet_is_refused() {
822        let (mut send, mut recv) = pair();
823        let protected = send.protect(&rtp(100, b"hello")).expect("protects");
824
825        recv.unprotect(&protected).expect("the first time");
826        assert_eq!(recv.unprotect(&protected), Err(SrtpError::Replayed(100)));
827    }
828
829    #[test]
830    fn out_of_order_packets_inside_the_window_are_accepted_once_each() {
831        let (mut send, mut recv) = pair();
832        let packets: Vec<Vec<u8>> = (200..210)
833            .map(|n| send.protect(&rtp(n, b"x")).expect("protects"))
834            .collect();
835
836        // Delivered backwards, which a network does.
837        for protected in packets.iter().rev() {
838            recv.unprotect(protected).expect("accepted once");
839        }
840        // And not a second time.
841        for protected in &packets {
842            assert!(matches!(
843                recv.unprotect(protected),
844                Err(SrtpError::Replayed(_))
845            ));
846        }
847    }
848
849    /// Older than the window can judge is refused rather than accepted. Accepting it would make
850    /// the window a speed bump: an attacker would only have to wait.
851    #[test]
852    fn a_packet_older_than_the_window_is_refused() {
853        let (mut send, mut recv) = pair();
854        let old = send.protect(&rtp(1, b"x")).expect("protects");
855        for n in 2..200 {
856            let p = send.protect(&rtp(n, b"x")).expect("protects");
857            recv.unprotect(&p).expect("accepted");
858        }
859        assert_eq!(recv.unprotect(&old), Err(SrtpError::Replayed(1)));
860    }
861
862    /// The sequence number wraps every twenty minutes at speech packet rates, and the rollover
863    /// counter it implies is never transmitted. Both ends infer it; an implementation that
864    /// infers differently decrypts to noise from that moment on.
865    #[test]
866    fn the_stream_survives_the_sequence_number_wrapping() {
867        let (mut send, mut recv) = pair();
868        for n in [65_530u16, 65_533, 65_535, 0, 1, 5] {
869            let plain = rtp(n, b"across the wrap");
870            let protected = send.protect(&plain).expect("protects");
871            assert_eq!(
872                recv.unprotect(&protected).expect("unprotects"),
873                plain,
874                "sequence {n} did not survive"
875            );
876        }
877        assert_eq!(send.roc, 1, "the sender counted one rollover");
878        assert_eq!(recv.roc, 1, "and so did the receiver");
879    }
880
881    #[test]
882    fn rtcp_round_trips_and_is_encrypted() {
883        let (mut send, mut recv) = pair();
884        // A minimal receiver report: version 2, PT 201, then the sender SSRC and a body.
885        let mut packet = vec![0x80, 201, 0x00, 0x07];
886        packet.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
887        packet.extend_from_slice(b"REPORTBODY-REPORTBODY-RE");
888
889        let protected = send.protect_rtcp(&packet).expect("protects");
890        assert!(
891            !protected.windows(6).any(|w| w == b"REPORT"),
892            "the report body must not appear on the wire"
893        );
894        assert_eq!(recv.unprotect_rtcp(&protected).expect("unprotects"), packet);
895    }
896
897    fn rtcp() -> Vec<u8> {
898        let mut packet = vec![0x80, 201, 0x00, 0x07];
899        packet.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
900        packet.extend_from_slice(b"REPORTBODY-REPORTBODY-RE");
901        packet
902    }
903
904    /// RFC 3711 §3.4 applies §3.3.2's replay rule to the explicit SRTCP index. A genuine captured
905    /// report authenticates forever, so authentication alone cannot reject its second delivery.
906    #[test]
907    fn an_authenticated_srtcp_packet_is_accepted_once() {
908        let (mut send, mut recv) = pair();
909        let first = send.protect_rtcp(&rtcp()).expect("protects index zero");
910        let second = send.protect_rtcp(&rtcp()).expect("protects index one");
911
912        recv.unprotect_rtcp(&first).expect("accepted once");
913        assert_eq!(recv.unprotect_rtcp(&first), Err(SrtpError::ReplayedRtcp(0)));
914        recv.unprotect_rtcp(&second)
915            .expect("a distinct authenticated index remains acceptable");
916    }
917
918    /// RFC 3711 §3.4 says the SRTCP replay list is separate from the SRTP list. Both streams begin
919    /// at index zero, and advancing one must not consume the other's bit zero.
920    #[test]
921    fn srtp_and_srtcp_have_separate_replay_windows() {
922        let (mut send, mut recv) = pair();
923        let media = send.protect(&rtp(0, b"audio")).expect("protects RTP zero");
924        let control = send.protect_rtcp(&rtcp()).expect("protects RTCP zero");
925
926        recv.unprotect(&media).expect("RTP zero is accepted");
927        recv.unprotect_rtcp(&control)
928            .expect("SRTCP zero is independently accepted");
929        assert_eq!(recv.unprotect(&media), Err(SrtpError::Replayed(0)));
930        assert_eq!(
931            recv.unprotect_rtcp(&control),
932            Err(SrtpError::ReplayedRtcp(0))
933        );
934    }
935
936    /// RFC 3711 §3.3 step 5 authenticates before touching replay state. Changing the explicit index
937    /// without recomputing the tag is a forged high-index packet and cannot push the window ahead.
938    #[test]
939    fn a_forged_high_srtcp_index_does_not_advance_the_window() {
940        let (mut send, mut recv) = pair();
941        let authentic = send.protect_rtcp(&rtcp()).expect("protects");
942        let mut forged = authentic.clone();
943        let trailer = forged.len() - TAG_LEN - 4;
944        forged[trailer..trailer + 4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
945
946        assert_eq!(recv.unprotect_rtcp(&forged), Err(SrtpError::NotAuthentic));
947        recv.unprotect_rtcp(&authentic)
948            .expect("the authentic index zero was not made old");
949    }
950
951    /// A 64-packet window holds distances zero through 63. An unseen packet exactly 63 behind is
952    /// accepted once; one exactly 64 behind is too old. Testing both edges pins the comparison,
953    /// rather than merely observing a packet comfortably outside the window.
954    #[test]
955    fn the_srtcp_replay_window_holds_exactly_sixty_four_indices() {
956        let (mut send, mut recv) = pair();
957        let oldest_held = send.protect_rtcp(&rtcp()).expect("protects index zero");
958        send.rtcp_index = 63;
959        let newest = send.protect_rtcp(&rtcp()).expect("protects index 63");
960        recv.unprotect_rtcp(&newest).expect("establishes index 63");
961        recv.unprotect_rtcp(&oldest_held)
962            .expect("an unseen packet 63 places behind remains held");
963        assert_eq!(
964            recv.unprotect_rtcp(&oldest_held),
965            Err(SrtpError::ReplayedRtcp(0))
966        );
967
968        let (mut send, mut recv) = pair();
969        let too_old = send.protect_rtcp(&rtcp()).expect("protects index zero");
970        send.rtcp_index = 64;
971        let newest = send.protect_rtcp(&rtcp()).expect("protects index 64");
972        recv.unprotect_rtcp(&newest).expect("establishes index 64");
973        assert_eq!(
974            recv.unprotect_rtcp(&too_old),
975            Err(SrtpError::ReplayedRtcp(0))
976        );
977    }
978
979    /// The SRTCP index is 31 bits. The window treats `0x7fff_ffff -> 0` as one forward step, not as
980    /// an ancient packet, and still remembers the last pre-wrap packet after crossing the boundary.
981    #[test]
982    fn the_srtcp_replay_window_crosses_the_index_wrap() {
983        let (mut send, mut recv) = pair();
984        send.rtcp_index = 0x7FFF_FFFE;
985        let before = send.protect_rtcp(&rtcp()).expect("protects max minus one");
986        let last = send.protect_rtcp(&rtcp()).expect("protects max");
987        let wrapped = send.protect_rtcp(&rtcp()).expect("protects zero");
988
989        recv.unprotect_rtcp(&before).expect("accepts max minus one");
990        recv.unprotect_rtcp(&last).expect("accepts max");
991        recv.unprotect_rtcp(&wrapped).expect("accepts wrapped zero");
992        assert_eq!(
993            recv.unprotect_rtcp(&last),
994            Err(SrtpError::ReplayedRtcp(0x7FFF_FFFF))
995        );
996    }
997
998    #[test]
999    fn an_altered_rtcp_packet_is_refused() {
1000        let (mut send, mut recv) = pair();
1001        let mut packet = vec![0x80, 201, 0x00, 0x07];
1002        packet.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
1003        packet.extend_from_slice(b"REPORTBODY-REPORTBODY-RE");
1004
1005        let mut protected = send.protect_rtcp(&packet).expect("protects");
1006        protected[10] ^= 0x01;
1007        assert_eq!(
1008            recv.unprotect_rtcp(&protected),
1009            Err(SrtpError::NotAuthentic)
1010        );
1011    }
1012
1013    #[test]
1014    fn a_wrong_length_key_is_refused_by_name() {
1015        let error = Context::new(&[0u8; 8], &[0u8; 14]).expect_err("refused");
1016        assert!(error.to_string().contains("master key"), "{error}");
1017        let error = Context::new(&[0u8; 16], &[0u8; 4]).expect_err("refused");
1018        assert!(error.to_string().contains("master salt"), "{error}");
1019    }
1020
1021    /// A header with CSRCs is longer, and encrypting from the wrong offset would encrypt part of
1022    /// the header and leave part of the audio in the clear.
1023    #[test]
1024    fn a_header_with_contributing_sources_is_measured_correctly() {
1025        let mut packet = vec![0x82, 0x00, 0x00, 0x05]; // two CSRCs
1026        packet.extend_from_slice(&800u32.to_be_bytes());
1027        packet.extend_from_slice(&0xDEAD_BEEFu32.to_be_bytes());
1028        packet.extend_from_slice(&1u32.to_be_bytes());
1029        packet.extend_from_slice(&2u32.to_be_bytes());
1030        packet.extend_from_slice(b"AUDIOAUDIO");
1031
1032        assert_eq!(rtp_header_len(&packet), Some(20));
1033
1034        let (mut send, mut recv) = pair();
1035        let protected = send.protect(&packet).expect("protects");
1036        assert_eq!(&protected[..20], &packet[..20], "the whole header is clear");
1037        assert!(!protected.windows(5).any(|w| w == b"AUDIO"));
1038        assert_eq!(recv.unprotect(&protected).expect("unprotects"), packet);
1039    }
1040
1041    #[test]
1042    fn a_truncated_packet_is_refused_rather_than_indexed() {
1043        let (_, mut recv) = pair();
1044        assert!(matches!(
1045            recv.unprotect(&[0u8; 4]),
1046            Err(SrtpError::TooShort(4))
1047        ));
1048        assert_eq!(rtp_header_len(&[0u8; 8]), None);
1049    }
1050
1051    /// Keys must not reach a log through a derived `Debug`.
1052    #[test]
1053    fn debug_output_does_not_leak_key_material() {
1054        let context = Context::new(&[7u8; 16], &[9u8; 14]).expect("a context");
1055        let printed = format!("{context:?}");
1056        assert!(printed.contains("Session { .. }"), "{printed}");
1057        assert!(!printed.contains('7'), "{printed}");
1058    }
1059}