PR #23249 opened by michaelni
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23249
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23249.patch
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]>
From 14b662a4d072994c5a5cfcc4bef866ae2ee49776 Mon Sep 17 00:00:00 2001
From: Omkhar Arasaratnam <[email protected]>
Date: Thu, 21 May 2026 00:00:00 +0000
Subject: [PATCH] avformat/whip: require remote DTLS fingerprint in SDP answer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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]>
---
libavformat/whip.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/libavformat/whip.c b/libavformat/whip.c
index 47b80e9960..edd9136d3e 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -290,6 +290,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:
@@ -921,6 +923,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 */
@@ -970,6 +983,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_relative();
@@ -2130,6 +2154,7 @@ static av_cold void whip_deinit(AVFormatContext *s)
ffurl_closep(&whip->dtls_uc);
ffurl_closep(&whip->udp);
av_freep(&whip->dtls_fingerprint);
+ av_freep(&whip->remote_fingerprint);
}
static int whip_check_bitstream(AVFormatContext *s, AVStream *st, const
AVPacket *pkt)
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]