tls: Add support for listen mode

Also add options for specifying a certificate and key, which can
be used both when operating as client and as server.

Partially based on a patch by Peter Ross.

Signed-off-by: Martin Storsjö <martin@martin.st>
This commit is contained in:
Martin Storsjö 2013-09-19 12:30:52 +03:00
parent 5c53bf7aaf
commit 705b748e8d
3 changed files with 55 additions and 8 deletions

View File

@ -599,6 +599,19 @@ the host name is validated as well.)
This is disabled by default since it requires a CA database to be This is disabled by default since it requires a CA database to be
provided by the caller in many cases. provided by the caller in many cases.
@item cert_file
A file containing a certificate to use in the handshake with the peer.
(When operating as server, in listen mode, this is more often required
by the peer, while client certificates only are mandated in certain
setups.)
@item key_file
A file containing the private key for the certificate.
@item listen=@var{1|0}
If enabled, listen for connections on the provided port, and assume
the server role in the handshake instead of the client role.
@end table @end table
@section udp @section udp

View File

@ -23,6 +23,7 @@
#include "url.h" #include "url.h"
#include "libavutil/avstring.h" #include "libavutil/avstring.h"
#include "libavutil/opt.h" #include "libavutil/opt.h"
#include "libavutil/parseutils.h"
#if CONFIG_GNUTLS #if CONFIG_GNUTLS
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include <gnutls/x509.h> #include <gnutls/x509.h>
@ -69,6 +70,9 @@ typedef struct {
int fd; int fd;
char *ca_file; char *ca_file;
int verify; int verify;
char *cert_file;
char *key_file;
int listen;
} TLSContext; } TLSContext;
#define OFFSET(x) offsetof(TLSContext, x) #define OFFSET(x) offsetof(TLSContext, x)
@ -77,6 +81,9 @@ typedef struct {
static const AVOption options[] = { static const AVOption options[] = {
{"ca_file", "Certificate Authority database file", OFFSET(ca_file), AV_OPT_TYPE_STRING, .flags = D|E }, {"ca_file", "Certificate Authority database file", OFFSET(ca_file), AV_OPT_TYPE_STRING, .flags = D|E },
{"tls_verify", "Verify the peer certificate", OFFSET(verify), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, {"tls_verify", "Verify the peer certificate", OFFSET(verify), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E },
{"cert_file", "Certificate file", OFFSET(cert_file), AV_OPT_TYPE_STRING, .flags = D|E },
{"key_file", "Private key file", OFFSET(key_file), AV_OPT_TYPE_STRING, .flags = D|E },
{"listen", "Listen for incoming connections", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E },
{ NULL } { NULL }
}; };
@ -135,7 +142,7 @@ static int tls_open(URLContext *h, const char *uri, int flags)
TLSContext *c = h->priv_data; TLSContext *c = h->priv_data;
int ret; int ret;
int port; int port;
char buf[200], host[200]; char buf[200], host[200], opts[50] = "";
int numerichost = 0; int numerichost = 0;
struct addrinfo hints = { 0 }, *ai = NULL; struct addrinfo hints = { 0 }, *ai = NULL;
const char *proxy_path; const char *proxy_path;
@ -143,8 +150,11 @@ static int tls_open(URLContext *h, const char *uri, int flags)
ff_tls_init(); ff_tls_init();
if (c->listen)
snprintf(opts, sizeof(opts), "?listen=1");
av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, NULL, 0, uri); av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, NULL, 0, uri);
ff_url_join(buf, sizeof(buf), "tcp", NULL, host, port, NULL); ff_url_join(buf, sizeof(buf), "tcp", NULL, host, port, "%s", opts);
hints.ai_flags = AI_NUMERICHOST; hints.ai_flags = AI_NUMERICHOST;
if (!getaddrinfo(host, NULL, &hints, &ai)) { if (!getaddrinfo(host, NULL, &hints, &ai)) {
@ -174,8 +184,8 @@ static int tls_open(URLContext *h, const char *uri, int flags)
c->fd = ffurl_get_file_handle(c->tcp); c->fd = ffurl_get_file_handle(c->tcp);
#if CONFIG_GNUTLS #if CONFIG_GNUTLS
gnutls_init(&c->session, GNUTLS_CLIENT); gnutls_init(&c->session, c->listen ? GNUTLS_SERVER : GNUTLS_CLIENT);
if (!numerichost) if (!c->listen && !numerichost)
gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, host, strlen(host)); gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, host, strlen(host));
gnutls_certificate_allocate_credentials(&c->cred); gnutls_certificate_allocate_credentials(&c->cred);
if (c->ca_file) if (c->ca_file)
@ -186,6 +196,18 @@ static int tls_open(URLContext *h, const char *uri, int flags)
#endif #endif
gnutls_certificate_set_verify_flags(c->cred, c->verify ? gnutls_certificate_set_verify_flags(c->cred, c->verify ?
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT : 0); GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT : 0);
if (c->cert_file && c->key_file) {
ret = gnutls_certificate_set_x509_key_file(c->cred,
c->cert_file, c->key_file,
GNUTLS_X509_FMT_PEM);
if (ret < 0) {
av_log(h, AV_LOG_ERROR,
"Unable to set cert/key files %s and %s: %s\n",
c->cert_file, c->key_file, gnutls_strerror(ret));
ret = AVERROR(EIO);
goto fail;
}
}
gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred); gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred);
gnutls_transport_set_ptr(c->session, (gnutls_transport_ptr_t) gnutls_transport_set_ptr(c->session, (gnutls_transport_ptr_t)
(intptr_t) c->fd); (intptr_t) c->fd);
@ -230,7 +252,7 @@ static int tls_open(URLContext *h, const char *uri, int flags)
} }
} }
#elif CONFIG_OPENSSL #elif CONFIG_OPENSSL
c->ctx = SSL_CTX_new(TLSv1_client_method()); c->ctx = SSL_CTX_new(c->listen ? TLSv1_server_method() : TLSv1_client_method());
if (!c->ctx) { if (!c->ctx) {
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL)); av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ret = AVERROR(EIO); ret = AVERROR(EIO);
@ -238,6 +260,18 @@ static int tls_open(URLContext *h, const char *uri, int flags)
} }
if (c->ca_file) if (c->ca_file)
SSL_CTX_load_verify_locations(c->ctx, c->ca_file, NULL); SSL_CTX_load_verify_locations(c->ctx, c->ca_file, NULL);
if (c->cert_file && !SSL_CTX_use_certificate_chain_file(c->ctx, c->cert_file)) {
av_log(h, AV_LOG_ERROR, "Unable to load cert file %s: %s\n",
c->cert_file, ERR_error_string(ERR_get_error(), NULL));
ret = AVERROR(EIO);
goto fail;
}
if (c->key_file && !SSL_CTX_use_PrivateKey_file(c->ctx, c->key_file, SSL_FILETYPE_PEM)) {
av_log(h, AV_LOG_ERROR, "Unable to load key file %s: %s\n",
c->key_file, ERR_error_string(ERR_get_error(), NULL));
ret = AVERROR(EIO);
goto fail;
}
// Note, this doesn't check that the peer certificate actually matches // Note, this doesn't check that the peer certificate actually matches
// the requested hostname. // the requested hostname.
if (c->verify) if (c->verify)
@ -249,10 +283,10 @@ static int tls_open(URLContext *h, const char *uri, int flags)
goto fail; goto fail;
} }
SSL_set_fd(c->ssl, c->fd); SSL_set_fd(c->ssl, c->fd);
if (!numerichost) if (!c->listen && !numerichost)
SSL_set_tlsext_host_name(c->ssl, host); SSL_set_tlsext_host_name(c->ssl, host);
while (1) { while (1) {
ret = SSL_connect(c->ssl); ret = c->listen ? SSL_accept(c->ssl) : SSL_connect(c->ssl);
if (ret > 0) if (ret > 0)
break; break;
if (ret == 0) { if (ret == 0) {

View File

@ -31,7 +31,7 @@
#define LIBAVFORMAT_VERSION_MAJOR 55 #define LIBAVFORMAT_VERSION_MAJOR 55
#define LIBAVFORMAT_VERSION_MINOR 5 #define LIBAVFORMAT_VERSION_MINOR 5
#define LIBAVFORMAT_VERSION_MICRO 2 #define LIBAVFORMAT_VERSION_MICRO 3
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \ LIBAVFORMAT_VERSION_MINOR, \