This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch release/8.0 in repository ffmpeg.
commit d7b12b918a9614b69e6c9c3f042531115fe866d4 Author: Omkhar Arasaratnam <[email protected]> AuthorDate: Thu May 21 00:00:00 2026 +0000 Commit: Michael Niedermayer <[email protected]> CommitDate: Sun Jun 14 04:59:03 2026 +0200 avformat/whip: require remote DTLS fingerprint in SDP answer WHIP relies on the SDP a=fingerprint to bind the peer identity to the SRTP keying material (RFC 8842 §§ 5.1, 5.3). parse_answer() walks the SDP body looking for `a=ice-lite`, `a=ice-ufrag:`, `a=ice-pwd:`, and `a=candidate:` lines and ignores everything else, including `a=fingerprint`. WHIP intentionally runs the OpenSSL DTLS backend with s->verify=0 (DTLS-SRTP uses self-signed certs by design, so a CA chain check would be meaningless); the peer cert is accepted regardless of identity. The spec's compensating identity control — verifying the DTLS peer cert against the SDP a=fingerprint hash — is missing, so the SRTP keys are derived from a session whose peer identity was never authenticated. Any on-path attacker, or a malicious WHIP server URL, can substitute their own DTLS material and the publisher's RTP stream will be encrypted to them. The full fix is a two-step process: (a) Require a=fingerprint to be present in the SDP answer (RFC 8842 § 5.3 MUST). (b) After DTLS handshake, compute the peer-cert hash and compare against the stored fingerprint (RFC 8842 § 5.1 MUST); tear down the session on mismatch. This patch implements step (a) only; step (b) requires plumbing the cert hash out of dtls_start() through the openssl TLS context and is left as a follow-up. Add a remote_fingerprint field to WHIPContext, extract the a=fingerprint value from the SDP answer in parse_answer(), and return AVERROR(EINVAL) when it is absent. This raises the bar from "no fingerprint required" to "any fingerprint required"; the follow-up patch will raise it again to "fingerprint MUST hash-match the DTLS peer cert." Found-by: Claude (Anthropic). Human-verified and reported by Omkhar Arasaratnam <[email protected]>. Signed-off-by: Omkhar Arasaratnam <[email protected]> (cherry picked from commit 7b46c6a2a33376dc438c75456371669b7ebd8e40) Signed-off-by: Michael Niedermayer <[email protected]> --- libavformat/whip.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/libavformat/whip.c b/libavformat/whip.c index fcac19d1c2..bb32816c65 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -263,6 +263,8 @@ typedef struct WHIPContext { char key_buf[MAX_CERTIFICATE_SIZE]; /* The fingerprint of certificate, used in SDP offer. */ char *dtls_fingerprint; + /* remote DTLS cert fingerprint from SDP answer (sha-256). */ + char *remote_fingerprint; /** * This represents the material used to build the SRTP master key. It is * generated by DTLS and has the following layout: @@ -880,6 +882,17 @@ static int parse_answer(AVFormatContext *s) ret = AVERROR(ENOMEM); goto end; } + } else if (av_strstart(line, "a=fingerprint:", &ptr) && !whip->remote_fingerprint) { + /* SDP a=fingerprint format is "<algo> <hex:hex:...>". Skip + * the algo token, store the hex string for post-handshake compare. */ + const char *space = strchr(ptr, ' '); + if (space) { + whip->remote_fingerprint = av_strdup(space + 1); + if (!whip->remote_fingerprint) { + ret = AVERROR(ENOMEM); + goto end; + } + } } else if (av_strstart(line, "a=candidate:", &ptr) && !whip->ice_protocol) { if (ptr && av_stristr(ptr, "host")) { /* Refer to RFC 5245 15.1 */ @@ -929,6 +942,17 @@ static int parse_answer(AVFormatContext *s) goto end; } + /* per RFC 8829/8842, SDP answer MUST carry a=fingerprint and that + * fingerprint MUST match the DTLS peer certificate. Without it, an + * on-path attacker can complete DTLS with an arbitrary self-signed + * certificate and the resulting SRTP session is unauthenticated. */ + if (!whip->remote_fingerprint || !strlen(whip->remote_fingerprint)) { + av_log(whip, AV_LOG_ERROR, + "No remote DTLS fingerprint in SDP answer; refusing unauthenticated session\n"); + ret = AVERROR(EINVAL); + goto end; + } + if (whip->state < WHIP_STATE_NEGOTIATED) whip->state = WHIP_STATE_NEGOTIATED; whip->whip_answer_time = av_gettime(); @@ -1876,6 +1900,7 @@ static av_cold void whip_deinit(AVFormatContext *s) ff_srtp_free(&whip->srtp_recv); ffurl_close(whip->dtls_uc); ffurl_closep(&whip->udp); + av_freep(&whip->remote_fingerprint); } static int whip_check_bitstream(AVFormatContext *s, AVStream *st, const AVPacket *pkt) _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
