Skip to content

Encryption/Decryption issue with video from freeSwitch, when extension SIP Bypass was set to Proxy Media #3119

Description

@weic-harding

Describe the bug
SoftPhone calling Physical Doorphone, Remote Video not coming back, when extension SIP Bypass was set to Proxy Media, mandatory Media Encryption, transport TLS

github_issue_log.txt

Environment
FreeSWITCH version: 1.11.1-release git c2c5964 2026-05-26 20:41:26Z 64bit
Install: FusionPBX on Debian 13, AWS EC2
SIP profile: internal, TLS transport, SDES-SRTP (AES_CM_128_HMAC_SHA1_80)
Media mode: extension configured with "Proxy Media" (SIP Bypass Media = Proxy Media) enabled
Call type: two-party bridge, audio + video (H.264), between an internal extension and a SIP video endpoint (door phone / hardware video intercom)
Summary

When Proxy Media is enabled on an extension carrying a video+SRTP call, a mid-call SDP refresh (observed here on hold/resume and on automatic video-port re-learning) causes one leg to send an outgoing "Local SDP" body that is actually the other leg's SDP — including that leg's own o= origin line pointing at the other party's device IP, and that leg's own SRTP key. The affected leg's own internally pre-computed candidate keys (logged via Set Local audio/video crypto Key) are never what actually ends up in its outgoing SDP.

Net effect: the endpoint on the affected leg ends up encrypting its own outgoing media with a key that was never negotiated with it, and receiving media it can never decrypt, for the rest of that media session. This reproduces on effectively every Proxy Media call that survives long enough to hit a refresh; it does not reproduce under Bypass Media or Default media mode.

Steps To Reproduce
Enable Proxy Media on an extension (FusionPBX: Extension → Advanced Settings → SIP Bypass Media = Proxy Media).
Place a bridged call from that extension to another SRTP+video-capable endpoint.
Let the call run long enough to trigger a mid-call SDP refresh — in our testing this happened naturally around 10-100 seconds in via automatic video port re-learning (Auto Changing video port ...), and can also be triggered on demand via a client-side hold/resume (pause/resume video).
Capture /var/log/freeswitch/freeswitch.log at DEBUG level (or at least switch_core_media / sofia_glue debug) for both channel UUIDs of the call, and take a simultaneous packet capture at the endpoint.
Expected behavior

Each leg continues to encrypt/decrypt using its own separately negotiated SRTP key after the refresh. Both directions of media remain valid for the life of the call.

Actual behavior

After the refresh (visible via sofia_glue.c:1625 Setting proxy route ... immediately preceding it in our logs), one leg's logged "Local SDP" block has:

o=- origin instead of o=FreeSWITCH ...
an IP address matching the other leg's remote endpoint, not this leg's own server IP
an a=crypto key that is identical to a key seen on the other channel UUID

We independently verified the practical impact by decrypting a matching packet capture with libsrtp, using the actual keys pulled from the log for both legs of one affected call:

The affected endpoint's outgoing media authenticated successfully (30/30 packets) against the other leg's key — i.e., it was using a key that was never negotiated with it, but did so consistently.
The same endpoint's incoming media failed SRTP authentication (0/30, "authentication failure") against every candidate key we had for that call, on both legs, in both crypto slots.

This is a permanent one-way break for the rest of that media session — not a transient glitch — and it recurs on every subsequent refresh, so client-side pause/resume never restores two-way video once it happens.

Log excerpt

See attached github_issue_log.txt (real domain, IP addresses, and SRTP key material redacted; the "same key appears on two different channel UUIDs" pattern is preserved by mapping each distinct key to a consistent placeholder).

Notes for triage

We haven't confirmed the exact line responsible, but the check in switch_core_media_build_crypto() in switch_core_media.c:

c
if (!force && engine->ssec[ctype].local_raw_key[0]) {
return SWITCH_STATUS_SUCCESS;
}

decides "key already generated" based only on the first byte of the key buffer being non-zero, rather than an explicit flag. Combined with whatever proxy-media re-negotiation path runs on refresh (sofia_glue.c proxy route handling), this looked like a plausible place for stale/cross-session state to leak, but we have not confirmed this with a debugger — flagging it only as a starting point, not a diagnosis.

Happy to provide the full (unredacted) log/pcap privately if that would help track this down.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions