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]
