From: Jack Lau via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: Jack Lau <code@ffmpeg.org>
Subject: [FFmpeg-devel] [PR] avformat/tls_gnutls: implement ff_ssl_gen_key_cert() (PR #21432)
Date: Sun, 11 Jan 2026 14:14:10 -0000
Message-ID: <176814085050.25.16376540336211401978@4457048688e7> (raw)
PR #21432 opened by Jack Lau (JackLau)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21432
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21432.patch
Generate self-signed cert and key in server mode if
there're no key and cert input.
Signed-off-by: Jack Lau <jacklau1222gm@gmail.com>
>From 26ddef616a5d7ec5a425cbcd36378ea2d7e710f8 Mon Sep 17 00:00:00 2001
From: Jack Lau <jacklau1222gm@gmail.com>
Date: Sun, 11 Jan 2026 21:56:16 +0800
Subject: [PATCH] avformat/tls_gnutls: implement ff_ssl_gen_key_cert()
Generate self-signed cert and key in server mode if
there're no key and cert input.
Signed-off-by: Jack Lau <jacklau1222gm@gmail.com>
---
libavformat/tls_gnutls.c | 237 +++++++++++++++++++++++++++++++++++++++
1 file changed, 237 insertions(+)
diff --git a/libavformat/tls_gnutls.c b/libavformat/tls_gnutls.c
index 6f58ec03d2..ccd2e261d6 100644
--- a/libavformat/tls_gnutls.c
+++ b/libavformat/tls_gnutls.c
@@ -30,8 +30,10 @@
#include "os_support.h"
#include "url.h"
#include "tls.h"
+#include "libavutil/intreadwrite.h"
#include "libavutil/opt.h"
#include "libavutil/thread.h"
+#include "libavutil/random_seed.h"
#ifndef GNUTLS_VERSION_NUMBER
#define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER
@@ -42,6 +44,219 @@
GCRY_THREAD_OPTION_PTHREAD_IMPL;
#endif
+#define MAX_MD_SIZE 64
+
+static int pkey_to_pem_string(gnutls_x509_privkey_t key, char *out, size_t out_sz)
+{
+ size_t read_bytes = 0;
+ int ret = 0;
+
+ if (!out || !out_sz)
+ return AVERROR(EINVAL);
+
+ gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, NULL, &read_bytes);
+ if (!read_bytes) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to read the private key size\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (read_bytes > out_sz) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Private key PEM string size %zu is large than %zu\n", read_bytes, out_sz);
+ return AVERROR(EINVAL);
+ }
+
+ ret = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, out, &read_bytes);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to export private key: %s\n", gnutls_strerror(ret));
+ return AVERROR(EINVAL);
+ }
+ out[read_bytes] = '\0';
+ return read_bytes;
+}
+
+static int crt_to_pem_string(gnutls_x509_crt_t crt, char *out, size_t out_sz)
+{
+ size_t read_bytes = 0;
+ int ret = 0;
+
+ if (!out || !out_sz)
+ return AVERROR(EINVAL);
+
+ gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &read_bytes);
+ if (!read_bytes) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to read the certificate size\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (read_bytes > out_sz) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Certificate PEM string size %zu is large than %zu\n", read_bytes, out_sz);
+ return AVERROR(EINVAL);
+ }
+
+ ret = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out, &read_bytes);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to export certificate: %s\n", gnutls_strerror(ret));
+ return AVERROR(EINVAL);
+ }
+ out[read_bytes] = '\0';
+ return read_bytes;
+}
+
+static int gnutls_x509_fingerprint(gnutls_x509_crt_t cert, char **fingerprint)
+{
+ unsigned char md[MAX_MD_SIZE];
+ size_t n = sizeof(md);
+ AVBPrint buf;
+ int ret;
+
+ ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, md, &n);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint, %s\n",
+ gnutls_strerror(ret));
+ return AVERROR(ENOMEM);
+ }
+
+ 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 gnutls_gen_private_key(gnutls_x509_privkey_t *key)
+{
+ int ret = 0;
+
+ ret = gnutls_x509_privkey_init(key);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to init private key: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_privkey_generate(*key, GNUTLS_PK_ECDSA,
+ gnutls_sec_param_to_pk_bits(GNUTLS_PK_ECDSA, GNUTLS_SEC_PARAM_MEDIUM), 0);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate private key: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+end:
+ return ret;
+}
+
+static int gnutls_gen_certificate(gnutls_x509_privkey_t key, gnutls_x509_crt_t *crt, char **fingerprint)
+{
+ int ret = 0;
+ uint64_t serial;
+ unsigned char buf[8];
+ const char *dn = "CN=lavf";
+
+ ret = gnutls_x509_crt_init(crt);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to init certificate: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_set_version(*crt, 3);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set certificate version: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ /**
+ * See https://gnutls.org/manual/gnutls.html#gnutls_005fx509_005fcrt_005fset_005fserial-1
+ * The provided serial should be a big-endian positive number (i.e. its leftmost bit should be zero).
+ */
+ serial = av_get_random_seed();
+ AV_WB64(buf, serial);
+ buf[0] &= 0x7F;
+ ret = gnutls_x509_crt_set_serial(*crt, buf, sizeof(buf));
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set certificate serial: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_set_activation_time(*crt, time(NULL));
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set certificate activation time: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_set_expiration_time(*crt, time(NULL) + 365 * 24 * 60 * 60);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set certificate expiration time: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_set_dn(*crt, dn, NULL);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set certificate dn: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_set_issuer_dn(*crt, dn, NULL);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set certificate issuer dn: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_set_key(*crt, key);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set key: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_crt_sign2(*crt, *crt, key, GNUTLS_DIG_SHA256, 0);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to sign certificate: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_x509_fingerprint(*crt, fingerprint);
+ if (ret < 0)
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint\n");
+end:
+ return ret;
+}
+
+int ff_ssl_gen_key_cert(char *key_buf, size_t key_sz, char *cert_buf, size_t cert_sz, char **fingerprint)
+{
+ int ret;
+ gnutls_x509_crt_t crt = NULL;
+ gnutls_x509_privkey_t key = NULL;
+
+ ret = gnutls_gen_private_key(&key);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate private key: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = gnutls_gen_certificate(key, &crt, fingerprint);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate certificate: %s\n", gnutls_strerror(ret));
+ goto end;
+ }
+
+ ret = pkey_to_pem_string(key, key_buf, key_sz);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to convert private key to PEM string\n");
+ goto end;
+ }
+
+ ret = crt_to_pem_string(crt, cert_buf, cert_sz);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "TLS: Failed to convert certificate to PEM string\n");
+ goto end;
+ }
+end:
+ if (crt)
+ gnutls_x509_crt_deinit(crt);
+ if (key)
+ gnutls_x509_privkey_deinit(key);
+ return ret;
+}
+
typedef struct TLSContext {
TLSShared tls_shared;
gnutls_session_t session;
@@ -263,6 +478,28 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
}
} else if (s->cert_file || s->key_file)
av_log(h, AV_LOG_ERROR, "cert and key required\n");
+
+ if (s->listen && !s->cert_file && !s->cert_buf && !s->key_file && !s->key_buf) {
+ gnutls_x509_crt_t cert = NULL;
+ gnutls_x509_privkey_t pkey = NULL;
+
+ av_log(h, AV_LOG_VERBOSE, "No server certificate provided, using self-signed\n");
+
+ ret = gnutls_gen_private_key(&pkey);
+ if (ret < 0)
+ goto fail;
+
+ ret = gnutls_gen_certificate(pkey, &cert, NULL);
+ if (ret < 0)
+ goto fail;
+
+ ret = gnutls_certificate_set_x509_key(c->cred, &cert, 1, pkey);
+ if (ret < 0) {
+ av_log(h, AV_LOG_ERROR, "Unable to set self-signed certificate: %s\n", gnutls_strerror(ret));
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ }
gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred);
gnutls_transport_set_pull_function(c->session, gnutls_url_pull);
gnutls_transport_set_push_function(c->session, gnutls_url_push);
--
2.49.1
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
reply other threads:[~2026-01-11 14:14 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=176814085050.25.16376540336211401978@4457048688e7 \
--to=ffmpeg-devel@ffmpeg.org \
--cc=code@ffmpeg.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git