§ 4.6  Module 4 — The Browser Gets a Dial Tone

Securing the Call: How DTLS-SRTP Won

SSL added and removed here! ☺— NSA "Google Cloud Exploitation" slide, disclosed by the Washington Post, October 2013

By the end of this lesson

  • Explain why SDES lost: signaling-path key visibility and the post-Snowden trust model
  • Trace the DTLS-SRTP flow: self-signed certs, a=fingerprint binding, a=setup roles, SRTP key export (RFC 5764)
  • Verify the fingerprint chain live: SDP fingerprint versus getStats certificate
  • State precisely what a compromised signaling server can and cannot do

Historical note

SRTP (RFC 3711, March 2004 — Baugher, McGrew, Näslund, Carrara, and Norrman, of Cisco and Ericsson) defined how to encrypt RTP and deliberately punted on the harder question: how the two ends agree on a key. Three answers competed. SDES (RFC 4568, July 2006 — Andreasen, Baugher, and Wing, all Cisco) simply wrote the SRTP master key, base64-encoded, into an a=crypto line of the SDP — trivially deployable, and quickly dominant in SIP trunking. ZRTP (Phil Zimmermann — the author of PGP, 1991 — with Alan Johnston and Jon Callas; RFC 6189, April 2011) ran a Diffie–Hellman exchange in the media path itself and had users read a short authentication string aloud to defeat middlemen. And DTLS-SRTP (RFC 5763 and RFC 5764, May 2010 — Fischl, Tschofenig, Rescorla; McGrew and Rescorla) ran a TLS handshake over the media path and derived SRTP keys from it. Through 2011–2013 the RTCWEB working group argued about whether WebRTC should also permit SDES for compatibility with deployed SIP gear — SDES's flaw, that every signaling hop can read the key, was documented but weighed against interop. Then, on June 5, 2013, the Guardian published the first Snowden document, and that October the Washington Post printed the MUSCULAR slide — the NSA's hand-drawn smiley at the exact point where Google's TLS terminated and traffic ran dark inside the private network. The lesson generalized instantly: any point in the middle that can read your keys eventually will be read. At IETF 88 in Vancouver (November 2013) the technical plenary, with Bruce Schneier presenting, treated pervasive surveillance as an engineering fault to be designed against; RFC 7258 (Farrell and Tschofenig, May 2014, BCP 188) made it official doctrine: "pervasive monitoring is an attack." SDES was politically dead in WebRTC, and the security architecture eventually published as RFC 8827 (Rescorla, January 2021) wrote the obituary in normative prose: WebRTC implementations must support DTLS-SRTP — and must not support SDES.

4.6.1A key in every mailbox: what SDES actually did

To see why SDES lost, look at what it put on the wire. An SDES-secured offer carries a line like this in each media section:

a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32

Listing 4.6.1 — an SDES a=crypto attribute (RFC 4568). The inline: parameter is the SRTP master key and salt — thirty octets, base64-encoded, in plaintext SDP.

That is not a hash, not a reference, not a wrapped secret: it is the key. Whoever reads this SDP can decrypt every SRTP packet of the session, and SDP is precisely the thing that signaling exists to carry. In a SIP deployment the offer transits your outbound proxy, perhaps a session border controller, perhaps several intermediate proxies, each of which terminates and re-originates the transport. "Just encrypt the signaling" does not help: SIP-over-TLS is hop-by-hop — each proxy decrypts, processes, and re-encrypts, so every hop holds the plaintext SDP and therefore the media key. The same is true of any WebRTC-style signaling service: the WebSocket server that relays your offers holds them in memory, likely logs them, and possibly backs the logs up.

The consequence is the one that mattered after 2013: compromise becomes passive and retroactive. An attacker — or a subpoena — that obtains the signaling logs plus a capture of the encrypted media can decrypt calls that ended months earlier, without ever having touched the media path while the call was live. Nobody had to target you; the keys were sitting in an archive, waiting. This is the precise property the MUSCULAR program exploited against inter-datacenter links, and it is why "the signaling server can read the key" went from a documented trade-off to an unacceptable design in a single news cycle.

4.6.2Moving key agreement onto the media path

DTLS-SRTP's answer is architectural, not cryptographic cleverness: run the key agreement over the same path the media takes, so the signaling plane never sees key material at all. The vehicle is DTLS — Datagram TLS, designed by Nagendra Modadugu and Eric Rescorla at Stanford (NDSS 2004), standardized as DTLS 1.0 in RFC 4347 (April 2006) and DTLS 1.2 in RFC 6347 (January 2012). DTLS is TLS re-plumbed for unreliable transport: it adds explicit sequence numbers, retransmission timers for handshake flights, and tolerance for reordered records, so a full TLS handshake can complete over UDP — which, after Module 3, you know is the only transport the media path realistically has.

Each RTCPeerConnection generates its own certificate when it is constructed — self-signed, ephemeral, telling you nothing about who the peer is. Chrome 52 (July 2016) switched the default from RSA-1024 to ECDSA P-256, cutting handshake CPU cost; the static RTCPeerConnection.generateCertificate() method lets an application pre-generate and persist one. Self-signed is the point, not a shortcut: there is no certificate authority that could meaningfully vouch for "this browser tab," so DTLS-SRTP does not even try. Authenticity is established by a different anchor, which is the subject of the next section.

The handshake itself runs over the candidate pair that ICE selected — the same five-tuple that will carry media moments later. That means three protocols share one UDP flow: STUN connectivity checks keep running, DTLS records carry the handshake (and later the SCTP data channels), and SRTP packets carry the media. The receiver demultiplexes by the first byte of each packet: 0–3 is STUN, 20–63 is DTLS, 128–191 is RTP or RTCP (RFC 7983). One port, one NAT binding, three protocols — a design forced by everything Module 3 taught about how expensive each additional NAT traversal is.

SDES (RFC 4568) — the key rides the signaling path Alice signaling server holds the key Bob a=crypto: … inline:<master key> SRTP — encrypted with a key the server already saw DTLS-SRTP (RFC 5764) — only a fingerprint rides the signaling path Alice signaling server sees no secrets Bob a=fingerprint:sha-256 7A:3E:… DTLS handshake on the ICE-selected pair → exported SRTP keys SRTP — keys exist only at the two endpoints
Figure 4.6.1 — The architectural difference. SDES makes the signaling plane a key custodian; DTLS-SRTP hands it only a certificate hash, which is verification data, not a secret.
A munged a=setup deadlocks DTLS while ICE reports successsecurity

The offerer sends a=setup:actpass; the answerer picks active (it sends the ClientHello) or passive. Munge both sides to passive and each waits for the other: ICE connects, consent checks pass, iceConnectionState reads connected, and connectionState sits at connecting until it eventually fails with no message identifying DTLS as the cause. The same deadlock occurs when a gateway hard-codes passive in both roles.

What to do

Never touch a=setup. When you see ICE connected but connectionState stuck, check the transport stats object: dtlsState stuck at connecting with a populated selectedCandidatePairId is the fingerprint of a role problem, and dtlsCipher being absent confirms no handshake completed.

4.6.3The fingerprint and the setup roles

If the certificates are self-signed, what stops anyone from presenting any certificate? The answer is two SDP attributes that every WebRTC offer and answer you have generated since § 4.3 has carried, and that you can now finally read:

m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=ice-ufrag:F7gI
a=ice-pwd:x9cml/YzichV2+XlhiMu8g
a=fingerprint:sha-256 7A:3E:91:0C:55:F2:88:1D:4B:C0:26:E9:73:AF:5A:10:8E:64:D2:39:BB:07:C5:E1:42:9F:6D:18:A4:50:CE:2B
a=setup:actpass

Listing 4.6.2 — the security attributes of a WebRTC media section. Even the m= line's transport token, UDP/TLS/RTP/SAVPF, names the stack: DTLS under secure RTP with feedback.

a=fingerprint (RFC 8122, March 2017 — Lennox and Holmberg, refining Lennox's RFC 4572 of July 2006) is a cryptographic hash — in practice SHA-256 — of the peer's DER-encoded certificate, printed as colon-separated uppercase hex. During the DTLS handshake, each side hashes the certificate the other actually presented and compares it against the fingerprint received in signaling. Match: the entity you are doing cryptography with is the entity that authored the SDP. Mismatch: the handshake is aborted and the transport fails — there is no prompt, no "proceed anyway," no unencrypted fallback. The fingerprint is the trust anchor of the entire call, and it is the only security-relevant thing the signaling plane carries.

a=setup settles who plays TLS client. The attribute predates WebRTC by years — RFC 4145 (September 2005, Yon and Camarillo) invented it for TCP media streams — and RFC 5763 §5 pins its use for DTLS-SRTP within offer/answer: the offerer must send a=setup:actpass ("either role; you choose"), and the answerer replies with active (it will initiate the handshake, acting as TLS client) or passive (it will listen, as server). Answerers almost universally choose active, for a latency reason worth remembering: the answerer can fire its ClientHello the instant its ICE checks succeed, without waiting for any further signaling round trip. Note the independence of roles here — the offerer is usually the ICE controlling agent, yet its counterpart is usually the DTLS initiator. The two state machines share a packet flow and nothing else.

Test yourself before moving on — the two questions below are answerable from this section alone.

Check your understanding

Auto-graded check

3 auto-graded questions with an explanation for every wrong answer. Requires JavaScript. (m4-l6-check1)

Interactive 3D instrument

How DTLS-SRTP won — where the key travels

A 3D instrument you drive yourself, one variable at a time. It needs JavaScript and WebGL, so it is not shown in this static copy of the page.

4.6.4From handshake to SRTP keys: RFC 5764

A subtlety that separates DTLS-SRTP from "media over DTLS": after the handshake completes, the media does not flow as DTLS application data. DTLS records would add per-packet overhead and hide the RTP header from the transport machinery (header extensions, RTCP feedback — Module 6 depends on reading those). Instead, RFC 5764 defines a TLS extension, use_srtp, sent in the ClientHello, through which the peers negotiate an SRTP protection profile; and once the handshake completes, both sides run the TLS keying-material exporter (RFC 5705, March 2010, Rescorla) with the fixed label EXTRACTOR-dtls_srtp to derive the SRTP master keys and salts from the DTLS master secret. Client-write and server-write keys fall out of one exporter call; the DTLS association itself stays up, carrying only handshake maintenance and SCTP data-channel traffic, while the audio and video ride SRTP with the exported keys.

Two families of profile matter in practice. The RFC 5764 originals are counter-mode AES with an HMAC tag — SRTP_AES128_CM_HMAC_SHA1_80 encrypts with AES-128-CM and authenticates with an 80-bit slice of HMAC-SHA1, two passes over every packet. RFC 7714 (December 2015, McGrew and Igoe) added the AEAD profiles SRTP_AEAD_AES_128_GCM and SRTP_AEAD_AES_256_GCM, which encrypt and authenticate in a single AES-GCM pass with a 16-octet tag — cheaper per packet on modern hardware with AES instructions, and structurally immune to the class of bugs where encryption and authentication disagree about what was processed. Browsers negotiate GCM when both ends offer it; you can see what won on any live connection through getStats:

const stats = await pc.getStats();
let transport;
stats.forEach((s) => {
  if (s.type === 'transport' && s.dtlsState === 'connected') transport = s;
});
console.log(transport.dtlsCipher);    // e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
console.log(transport.srtpCipher);    // e.g. "AES_CM_128_HMAC_SHA1_80"
console.log(transport.tlsVersion);    // "FEFD" — the wire bytes of DTLS 1.2

const localCert  = stats.get(transport.localCertificateId);
const remoteCert = stats.get(transport.remoteCertificateId);
console.log(remoteCert.fingerprintAlgorithm, remoteCert.fingerprint);
// ^ must equal the peer's a=fingerprint line — you will assert exactly this in Problem 1

Listing 4.6.3 — the transport→certificate walk. This is a recipe to reuse, not the full stats model; the complete stats graph is the subject of § 6.4.

The transport stats entry is the DTLS association's dashboard — dtlsState, negotiated ciphers, TLS version — and its localCertificateId/remoteCertificateId fields point at certificate entries carrying each end's fingerprint. Those fields close the loop on this lesson's central claim: the certificate that the DTLS handshake actually verified is observable from JavaScript, and it must equal the fingerprint that traveled through your signaling channel. Problem 1 has you verify that chain on a live connection — and then break it.

Offerer (passive) Answerer (active) offer: a=setup:actpass · a=fingerprint A (via signaling) answer: a=setup:active · a=fingerprint B (via signaling) ICE checks succeed — selected pair carries everything below ClientHello + use_srtp ServerHello, Certificate, … Certificate, Finished hash(cert) ≟ fingerprint B hash(cert) ≟ fingerprint A exporter → SRTP keys
Figure 4.6.2 — The DTLS-SRTP flow. The fingerprints cross the signaling plane; the keys never do. The active answerer speaks first the moment ICE completes.
DTLS-SRTP authenticates the signaling channel you already trustedsecurity

The DTLS certificates are self-signed. All that binds them to the peer is the a=fingerprint line carried over your signaling channel — so the security property is "encrypted against the network, given that signaling is honest". Your own signaling server can substitute fingerprints and MITM every call, and no browser indicator changes. Marketing copy that says "end-to-end encrypted" on a plain WebRTC deployment is describing hop-by-hop encryption.

What to do

If you need protection from your own infrastructure, you need media-layer E2EE (insertable streams or SFrame) plus an out-of-band identity binding — a short authentication string, or fingerprint pinning against keys distributed through a channel you do not control. Otherwise be accurate in what you claim.

4.6.5The threat model: what a bad signaling server buys

RFC 8826 and RFC 8827 (both Rescorla, January 2021) write down WebRTC's threat model with unusual candor, and its central assumption deserves to be stated plainly: the signaling server is not trusted with media confidentiality. Work through what a fully compromised signaling service can and cannot do, because Problem 2 will grade you on exactly this ledger.

It can read everything in the SDP: your codecs, your ICE candidates and therefore your network addresses, your certificate fingerprints, and — inescapably — the metadata of who called whom, when, for how long. It can refuse to relay your offer, silently failing any call it dislikes. It can rewrite the SDP in flight, and this is the one capability with teeth: replace the a=fingerprint in each direction with the fingerprint of its own certificate, place itself on the media path, and terminate two separate DTLS-SRTP associations — a full man-in-the-middle, with both hops genuinely encrypted, just each to the attacker. What it cannot do is the SDES failure mode: passively decrypt media, live or recorded. The SRTP keys are born in the DTLS handshake on the media path and exported locally at each end; no quantity of logged SDP contains them. A TURN relay, likewise, forwards SRTP ciphertext and never holds keys — relayed calls are exactly as confidential as direct ones.

The fingerprint-substitution attack is real but expensive for the attacker: it is active, it must persist on the media path for the call's entire life, and it leaves evidence — each endpoint now holds a remote certificate whose hash matches nothing the far end ever generated. Any comparison of fingerprints over a channel the signaling server does not control exposes it, which is ZRTP's spoken-string idea reborn. RFC 8827 §7 standardized a browser-level version: an identity provider (IdP) framework in which a third party — one you actually trust — signs your fingerprint, delivered via a=identity in the SDP and the peerIdentity API. It is the most carefully designed dead letter in WebRTC: Firefox implemented it, Chrome never did, no major service deployed it, and the ecosystem answered the problem at the application layer instead — apps that need MITM resistance against their own servers verify keys out of band or run end-to-end encryption above the transport, as Module 7 will show.

One more property is unique among the web's media technologies: WebRTC encryption has no off switch. There is no API to request plain RTP, no null cipher for media, no RTP/AVP answer a browser will accept; createOffer() emits a fingerprint whether you like it or not. This was a deliberate 2011-era decision, informed by the SIP world's experience, where "SRTP optional" had meant, in deployment after deployment, "SRTP off." Mandatory encryption removed the downgrade attack by removing the thing to downgrade to.

A common error

"My conference runs DTLS-SRTP, so it is end-to-end encrypted." Only if there are exactly two ends. In an SFU topology — which is how nearly every multi-party product works — each participant's DTLS-SRTP association terminates at the SFU. The server decrypts every packet, reads and rewrites RTP headers, and re-encrypts toward each receiver; it holds the keys to every hop and can access every encoded frame. DTLS-SRTP gives you hop-by-hop encryption with a hop in the middle. True end-to-end encryption through an SFU requires a second layer keyed only by the participants — SFrame over Insertable Streams — which is Module 7's subject.

Problems for § 4.6Check your understanding

First, verify the fingerprint trust chain on a real connection — and confirm that breaking it kills the transport; then account precisely for what a compromised signaling server can and cannot do.

Lab 1

Verify the Trust Anchor

Graded in the browser against 5 assertions; the editor and harness require JavaScript.

Check your understanding

Auto-graded check

5 auto-graded questions with an explanation for every wrong answer. Requires JavaScript. (m4-l6-ex2)

Field quirks

What the specifications say, and what the browsers actually do, are two different documents. These are the divergences that cost production teams their weekends.

Certificate generation is slow on mobile, and reusing one is a tracking vectorperf

Each RTCPeerConnection generates a fresh certificate. ECDSA P-256 (the default) takes single-digit milliseconds; RSA-2048 takes hundreds of milliseconds to over a second on low-end Android, which shows up as connection setup latency. RTCPeerConnection.generateCertificate() lets you pre-generate and pass the certificate into the constructor, amortising the cost across many connections — but a stable certificate means a stable fingerprint, which is a cross-session identifier, and certificates have an expires value (about 30 days by default) after which DTLS fails with nothing useful in the error.

What to do

Pre-generate at app start for mesh or multi-PC topologies, keep ECDSA, and check expires before reuse. Do not persist a certificate across sessions unless you have decided the identifier is acceptable.

SDES is gone, and the SRTP cipher you get is worth checkingsecurity

a=crypto (SDES, RFC 4568) was removed from browsers long ago and there is no flag to restore it, so legacy SBCs offering only SDES cannot interoperate at all. Among the ciphers that remain, AEAD_AES_128_GCM is negotiated only if both sides offer it; otherwise you fall back to AES_CM_128_HMAC_SHA1_80, which is both slower and has a shorter authentication tag. Nothing surfaces this choice in the UI, and an old SFU can quietly pin every call to the weaker suite.

What to do

Read srtpCipher and dtlsCipher from the transport stats object and record them per session. If your SFU is negotiating SHA1_80 across the board, that is a configuration fix with measurable CPU upside.

The Impossible Call · Module 4, Lesson 6