Skip to content

B Fixed RTCP unprotect replay window for GCM#1675

Merged
sipsorcery merged 2 commits into
sipsorcery-org:masterfrom
jimm98y:master
Jun 7, 2026
Merged

B Fixed RTCP unprotect replay window for GCM#1675
sipsorcery merged 2 commits into
sipsorcery-org:masterfrom
jimm98y:master

Conversation

@jimm98y
Copy link
Copy Markdown
Collaborator

@jimm98y jimm98y commented Jun 6, 2026

Same issue as in 8877339, only in RTCP. Removed the CheckAndUpdateReplayWindow method as it was no longer being used.

@sipsorcery
Copy link
Copy Markdown
Member

sipsorcery commented Jun 7, 2026

Below is from Claude Code:

Thanks for porting the replay-window fix to the RTCP path. The core change is correct — splitting into a read-only CheckReplayWindow before decrypt and UpdateReplayWindow only after authentication, with the AEAD decrypt wrapped so an auth failure returns without advancing the window, faithfully mirrors the RTP fix (8877339) and the RFC 3711 §3.3 ordering. Removing the now-dead CheckAndUpdateReplayWindow is fine too.

However, I think this introduces a functional regression in UnprotectRtcp for the encrypted path (AES-CM and AEAD GCM/CCM): the decrypt IV is still derived from ssrcContext.S_l rather than from this packet's index.

// ~line 1288 (AES-CM), ~1302 (AEAD GCM/CCM), ~1325 (Double AEAD outer)
SRTP.Encryption.CTR.GenerateMessageKeyIV (context.K_s, ssrc, ssrcContext.S_l, context.Iv16);
SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv12);

Previously this happened to work because the old CheckAndUpdateReplayWindow(index) ran before the decrypt and advanced S_l = index for newer packets, so S_l == index at IV-generation time. Now that the update is (correctly) deferred to after the decrypt, S_l still holds the previous packet's index when the IV is generated.

In-order stream:

  • Packet 1 (index 0): S_l defaults to 0 → IV uses 0, matches the sender → decrypts (this is why a single-packet round-trip passes).
  • Packet 2 (index 1): UpdateReplayWindow(0) left S_l = 0; CheckReplayWindow(1) does not touch it → IV uses 0 while the sender used 1 → wrong keystream / mac check in GCM failed. Every subsequent packet is off by one.

The RTP path avoids this by deriving the IV from the local index rather than S_l (e.g. GenerateMessageKeyIV(..., index, ...)). RTCP should do the same:

SRTP.Encryption.CTR.GenerateMessageKeyIV (context.K_s, ssrc, index, context.Iv16);
SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv12);   // GCM + Double AEAD outer

index already has the E-flag stripped (line ~1233), and the AAD continues to use originalIndex (with the E-flag), which is correct.

Net effect without this change: encrypted RTCP (SR/RR, NACK/PLI/REMB, BYE) stops decrypting after the first report for the standard WebRTC profiles — and this path worked before the PR, so it's a regression rather than a pre-existing issue.

The green CI looks to be a coverage gap: there's no multi-packet encrypted-RTCP round-trip test for CM/GCM. A two-packet GCM protect→unprotect round trip fails on packet 2 and catches this; happy to contribute one.

sipsorcery added a commit that referenced this pull request Jun 7, 2026
UnprotectRtcp must derive the per-packet AES-GCM IV from the SRTCP index
of the packet being decrypted, not from the connection's highest-seen
index (S_l). A single-packet round trip cannot detect a regression here
because S_l defaults to 0 and the first index is 0, so they coincide.

This test protects and unprotects an in-order multi-packet RTCP stream
and asserts every packet round-trips with its payload intact, which only
holds when the IV tracks each packet's own index. It guards against the
RTCP replay-window refactor in PR #1675 deriving the IV from a stale S_l
once the replay update is deferred until after authentication.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@jimm98y
Copy link
Copy Markdown
Collaborator Author

jimm98y commented Jun 7, 2026

Thank you very much! You're right, both about the issue and the missing test coverage. The issue should be fixed now, not sure when I get to writing the tests though, so any contributions/PRs are welcome!

@sipsorcery
Copy link
Copy Markdown
Member

Claude already wrote the test. Thanks for the update I'll give it a spin.

@sipsorcery sipsorcery merged commit d6ce0bb into sipsorcery-org:master Jun 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants