PR #23699 opened by Jack Lau (JackLau)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23699
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23699.patch

See RFC 5763 section 5:
If the fingerprint does not match the hashed certificate, then the
endpoint MUST tear down the media session immediately.

So this patch aims to implement the logic of verify peer's
fingerprint in WebRTC use case.

Add fp_verify option to specific we need to enable the
custom fingerprint verify callback, and add peer_fp option
to pass the peer's fingerprint.

Split mbedtls_x509_fingerprint function so it can be used
in both places.

Signed-off-by: Jack Lau <[email protected]>




>From 17c46c4bf721a825467e936817034f9f87e3bb35 Mon Sep 17 00:00:00 2001
From: Jack Lau <[email protected]>
Date: Sat, 4 Jul 2026 17:12:59 +0800
Subject: [PATCH] avformat/tls_{openssl, mbedtls, gnutls}: add DTLS fingerprint
 verify for WHIP

See RFC 5763 section 5:
If the fingerprint does not match the hashed certificate, then the
endpoint MUST tear down the media session immediately.

So this patch aims to implement the logic of verify peer's
fingerprint in WebRTC use case.

Add fp_verify option to specific we need to enable the
custom fingerprint verify callback, and add peer_fp option
to pass the peer's fingerprint.

Split mbedtls_x509_fingerprint function so it can be used
in both places.

Signed-off-by: Jack Lau <[email protected]>
---
 libavformat/tls.h         |  4 +++
 libavformat/tls_gnutls.c  | 49 ++++++++++++++++++++++++++++++++++
 libavformat/tls_mbedtls.c | 56 ++++++++++++++++++++++++++++++---------
 libavformat/tls_openssl.c | 25 +++++++++++++++++
 libavformat/whip.c        |  2 ++
 5 files changed, 123 insertions(+), 13 deletions(-)

diff --git a/libavformat/tls.h b/libavformat/tls.h
index f2f4f8991f..d43d72d6a4 100644
--- a/libavformat/tls.h
+++ b/libavformat/tls.h
@@ -57,6 +57,8 @@ typedef struct TLSShared {
     const AVClass *class;
     char *ca_file;
     int verify;
+    int fp_verify;
+    char *peer_fp;
     char *cert_file;
     char *key_file;
     int listen;
@@ -106,6 +108,8 @@ typedef struct TLSShared {
     {"mtu", "Maximum Transmission Unit", offsetof(pstruct, options_field . 
mtu), AV_OPT_TYPE_INT,  { .i64 = 0 }, 0, INT_MAX, .flags = TLS_OPTFL}, \
     {"cert_pem",   "Certificate PEM string",              offsetof(pstruct, 
options_field . cert_buf),  AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \
     {"key_pem",    "Private key PEM string",              offsetof(pstruct, 
options_field . key_buf),   AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \
+    {"fp_verify",  "Verify the peer certificate's fingerprint", 
offsetof(pstruct, options_field . fp_verify),    AV_OPT_TYPE_BOOL, { .i64 = 0 
}, 0, 1, .flags = TLS_OPTFL }, \
+    {"peer_fp",    "The peer certificates's fingerprint", offsetof(pstruct, 
options_field . peer_fp), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \
     FF_TLS_CLIENT_OPTIONS(pstruct, options_field)
 
 int ff_tls_parse_host(TLSShared *s, char *hostname, int hostname_size, int 
*port_ptr, const char *uri);
diff --git a/libavformat/tls_gnutls.c b/libavformat/tls_gnutls.c
index aedbc66e56..a9ee6d6bfb 100644
--- a/libavformat/tls_gnutls.c
+++ b/libavformat/tls_gnutls.c
@@ -32,6 +32,7 @@
 #include "os_support.h"
 #include "url.h"
 #include "tls.h"
+#include "libavutil/avstring.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
@@ -529,6 +530,51 @@ end:
     return ret;
 }
 
+/* Return value: 0 success, < 0 fail. */
+static int gnutls_verify_fingerprint(gnutls_session_t session)
+{
+    TLSContext *c = (TLSContext *)(gnutls_session_get_ptr(session));
+    TLSShared *s = &c->tls_shared;
+    unsigned int cert_list_size = 0;
+    const gnutls_datum_t *cert_list;
+    gnutls_x509_crt_t cert;
+    char *fingerprint = NULL;
+    int ret;
+
+    if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
+        av_log(c, AV_LOG_ERROR, "Unsupported certificate type\n");
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+    cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+    if (!cert_list || !cert_list_size) {
+        av_log(c, AV_LOG_ERROR, "No peer certificate for fingerprint 
verification\n");
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+    ret = gnutls_x509_crt_init(&cert);
+    if (ret < 0)
+        return GNUTLS_E_CERTIFICATE_ERROR;
+
+    ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
+    if (ret < 0) {
+        gnutls_x509_crt_deinit(cert);
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+    ret = gnutls_x509_fingerprint(cert, &fingerprint);
+    gnutls_x509_crt_deinit(cert);
+    if (ret < 0) {
+        ret = GNUTLS_E_CERTIFICATE_ERROR;
+        goto end;
+    }
+
+    ret = av_strcasecmp(s->peer_fp, fingerprint) ? GNUTLS_E_CERTIFICATE_ERROR 
: 0;
+end:
+    av_freep(&fingerprint);
+    return ret;
+}
+
 static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary 
**options)
 {
     TLSContext *c = h->priv_data;
@@ -561,6 +607,8 @@ static int tls_open(URLContext *h, const char *uri, int 
flags, AVDictionary **op
 #endif
     gnutls_certificate_set_verify_flags(c->cred, s->verify ?
                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT : 
0);
+    if (s->fp_verify && s->is_dtls && s->peer_fp)
+        gnutls_certificate_set_verify_function(c->cred, 
gnutls_verify_fingerprint);
     if (s->cert_file && s->key_file) {
         ret = gnutls_certificate_set_x509_key_file(c->cred,
                                                    s->cert_file, s->key_file,
@@ -624,6 +672,7 @@ static int tls_open(URLContext *h, const char *uri, int 
flags, AVDictionary **op
     }
 
     gnutls_init(&c->session, gnutls_flags);
+    gnutls_session_set_ptr(c->session, c);
 
     if (!s->listen && !s->numerichost)
         gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, s->host, 
strlen(s->host));
diff --git a/libavformat/tls_mbedtls.c b/libavformat/tls_mbedtls.c
index bfa5103596..e24dec98d8 100644
--- a/libavformat/tls_mbedtls.c
+++ b/libavformat/tls_mbedtls.c
@@ -44,12 +44,28 @@
 #include "libavutil/avstring.h"
 #include "libavutil/random_seed.h"
 
-static int mbedtls_x509_fingerprint(char *cert_buf, size_t cert_sz, char 
**fingerprint)
+static int mbedtls_x509_crt_fingerprint(const mbedtls_x509_crt *crt, char 
**fingerprint)
 {
     unsigned char md[32];
     size_t n = sizeof(md);
     AVBPrint buf;
     int ret;
+
+    if ((ret = mbedtls_sha256(crt->raw.p, crt->raw.len, md, 0)) != 0)
+        return AVERROR(EINVAL);
+
+    av_bprint_init(&buf, n*3, n*3);
+
+    for (int i = 0; i < n - 1; i++)
+        av_bprintf(&buf, "%02X:", md[i]);
+    av_bprintf(&buf, "%02X", md[n - 1]);
+
+    return av_bprint_finalize(&buf, fingerprint);
+}
+
+static int mbedtls_x509_fingerprint(char *cert_buf, size_t cert_sz, char 
**fingerprint)
+{
+    int ret;
     mbedtls_x509_crt crt;
 
     mbedtls_x509_crt_init(&crt);
@@ -59,18 +75,9 @@ static int mbedtls_x509_fingerprint(char *cert_buf, size_t 
cert_sz, char **finge
         return AVERROR(EINVAL);
     }
 
-    if ((ret = mbedtls_sha256(crt.raw.p, crt.raw.len, md, 0)) != 0) {
-        mbedtls_x509_crt_free(&crt);
-        return AVERROR(EINVAL);
-    }
-
-    av_bprint_init(&buf, n*3, n*3);
-
-    for (int i = 0; i < n - 1; i++)
-        av_bprintf(&buf, "%02X:", md[i]);
-    av_bprintf(&buf, "%02X", md[n - 1]);
-
-    return av_bprint_finalize(&buf, fingerprint);
+    ret = mbedtls_x509_crt_fingerprint(&crt, fingerprint);
+    mbedtls_x509_crt_free(&crt);
+    return ret;
 }
 
 int ff_ssl_read_key_cert(char *key_url, char *cert_url, char *key_buf, size_t 
key_sz, char *cert_buf, size_t cert_sz, char **fingerprint)
@@ -470,6 +477,27 @@ static void handle_handshake_error(URLContext *h, int ret)
     }
 }
 
+/* Return value: 0 success, < 0 fail. */
+static int mbedtls_verify_fingerprint(void *data, mbedtls_x509_crt *crt,
+                                      int depth, uint32_t *flags)
+{
+    TLSContext *tls_ctx = (TLSContext *)data;
+    TLSShared *shr = &tls_ctx->tls_shared;
+    char *fingerprint = NULL;
+    int ret;
+
+    ret = mbedtls_x509_crt_fingerprint(crt, &fingerprint);
+    if (ret < 0) {
+        ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
+        goto end;
+    }
+
+    ret = av_strcasecmp(shr->peer_fp, fingerprint) ? 
MBEDTLS_ERR_X509_CERT_VERIFY_FAILED : 0;
+end:
+    av_freep(&fingerprint);
+    return ret;
+}
+
 static int tls_handshake(URLContext *h)
 {
     TLSContext *tls_ctx = h->priv_data;
@@ -630,6 +658,8 @@ static int tls_open(URLContext *h, const char *uri, int 
flags, AVDictionary **op
     // not VERIFY_REQUIRED because we manually check after handshake
     mbedtls_ssl_conf_authmode(&tls_ctx->ssl_config,
                               shr->verify ? MBEDTLS_SSL_VERIFY_OPTIONAL : 
MBEDTLS_SSL_VERIFY_NONE);
+    if (shr->fp_verify && shr->is_dtls && shr->peer_fp)
+        mbedtls_ssl_conf_verify(&tls_ctx->ssl_config, 
mbedtls_verify_fingerprint, tls_ctx);
     mbedtls_ssl_conf_rng(&tls_ctx->ssl_config, mbedtls_ctr_drbg_random, 
&tls_ctx->ctr_drbg_context);
     mbedtls_ssl_conf_ca_chain(&tls_ctx->ssl_config, &tls_ctx->ca_cert, NULL);
 
diff --git a/libavformat/tls_openssl.c b/libavformat/tls_openssl.c
index 5d3be96fbb..0c3cd699e8 100644
--- a/libavformat/tls_openssl.c
+++ b/libavformat/tls_openssl.c
@@ -24,6 +24,7 @@
 
 #include "network.h"
 #include "os_support.h"
+#include "libavutil/mem.h"
 #include "libavutil/time.h"
 #include "libavutil/random_seed.h"
 #include "url.h"
@@ -775,6 +776,28 @@ fail:
     return ret;
 }
 
+/* Return value: 1 success, 0 fail. */
+static int openssl_verify_fingerprint(int preverify_ok, X509_STORE_CTX *ctx)
+{
+    int ret = 0;
+    char* fingerprint = NULL;
+       SSL *ssl = (SSL *)(X509_STORE_CTX_get_ex_data(ctx, 
SSL_get_ex_data_X509_STORE_CTX_idx()));
+       TLSContext *c = (TLSContext *)(SSL_get_ex_data(ssl, 0));
+       X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
+
+    ret = x509_fingerprint(cert, &fingerprint);
+    if (ret < 0) {
+        ret = 0;
+        goto end;
+    }
+
+    ret = av_strcasecmp(c->tls_shared.peer_fp, fingerprint) ? 0 : 1;
+
+end:
+    av_freep(&fingerprint);
+    return ret;
+}
+
 static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary 
**options)
 {
     TLSContext *c = h->priv_data;
@@ -814,6 +837,8 @@ static int tls_open(URLContext *h, const char *uri, int 
flags, AVDictionary **op
 
     if (s->verify)
         SSL_CTX_set_verify(c->ctx, 
SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+    else if (s->fp_verify && s->is_dtls && s->peer_fp)
+        SSL_CTX_set_verify(c->ctx, 
SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, openssl_verify_fingerprint);
 
     if (s->is_dtls && s->use_srtp) {
         /**
diff --git a/libavformat/whip.c b/libavformat/whip.c
index ae2c116a4d..a9233d64e4 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -398,6 +398,8 @@ static av_cold int dtls_initialize(AVFormatContext *s)
     av_dict_set_int(&opts, "listen", is_dtls_active ? 0 : 1, 0);
     // Do not verify CA
     av_dict_set_int(&opts, "verify", 0, 0);
+    av_dict_set_int(&opts, "fp_verify", 1, 0);
+    av_dict_set(&opts, "peer_fp", whip->remote_fingerprint, 0);
     ret = ffurl_open_whitelist(&whip->dtls_uc, buf, AVIO_FLAG_READ_WRITE, 
&s->interrupt_callback,
         &opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
     av_dict_free(&opts);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to