|
|
|
@ -22,9 +22,11 @@ |
|
|
|
|
#include "avformat.h" |
|
|
|
|
#include "url.h" |
|
|
|
|
#include "libavutil/avstring.h" |
|
|
|
|
#include "libavutil/opt.h" |
|
|
|
|
#include "libavutil/parseutils.h" |
|
|
|
|
#if CONFIG_GNUTLS |
|
|
|
|
#include <gnutls/gnutls.h> |
|
|
|
|
#include <gnutls/x509.h> |
|
|
|
|
#define TLS_read(c, buf, size) gnutls_record_recv(c->session, buf, size) |
|
|
|
|
#define TLS_write(c, buf, size) gnutls_record_send(c->session, buf, size) |
|
|
|
|
#define TLS_shutdown(c) gnutls_bye(c->session, GNUTLS_SHUT_RDWR) |
|
|
|
@ -66,8 +68,27 @@ typedef struct { |
|
|
|
|
SSL *ssl; |
|
|
|
|
#endif |
|
|
|
|
int fd; |
|
|
|
|
char *ca_file; |
|
|
|
|
int verify; |
|
|
|
|
} TLSContext; |
|
|
|
|
|
|
|
|
|
#define OFFSET(x) offsetof(TLSContext, x) |
|
|
|
|
#define D AV_OPT_FLAG_DECODING_PARAM |
|
|
|
|
#define E AV_OPT_FLAG_ENCODING_PARAM |
|
|
|
|
static const AVOption options[] = { |
|
|
|
|
{"ca_file", "Certificate Authority database file", OFFSET(ca_file), AV_OPT_TYPE_STRING, .flags = D|E }, |
|
|
|
|
{"cafile", "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 }, |
|
|
|
|
{ NULL } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static const AVClass tls_class = { |
|
|
|
|
.class_name = "tls", |
|
|
|
|
.item_name = av_default_item_name, |
|
|
|
|
.option = options, |
|
|
|
|
.version = LIBAVUTIL_VERSION_INT, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static int do_tls_poll(URLContext *h, int ret) |
|
|
|
|
{ |
|
|
|
|
TLSContext *c = h->priv_data; |
|
|
|
@ -115,7 +136,7 @@ static void set_options(URLContext *h, const char *uri) |
|
|
|
|
{ |
|
|
|
|
TLSContext *c = h->priv_data; |
|
|
|
|
char buf[1024], key[1024]; |
|
|
|
|
int has_cert, has_key, verify = 0; |
|
|
|
|
int has_cert, has_key; |
|
|
|
|
#if CONFIG_GNUTLS |
|
|
|
|
int ret; |
|
|
|
|
#endif |
|
|
|
@ -123,22 +144,14 @@ static void set_options(URLContext *h, const char *uri) |
|
|
|
|
if (!p) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
if (av_find_info_tag(buf, sizeof(buf), "cafile", p)) { |
|
|
|
|
#if CONFIG_GNUTLS |
|
|
|
|
ret = gnutls_certificate_set_x509_trust_file(c->cred, buf, GNUTLS_X509_FMT_PEM); |
|
|
|
|
if (ret < 0) |
|
|
|
|
av_log(h, AV_LOG_ERROR, "%s\n", gnutls_strerror(ret)); |
|
|
|
|
#elif CONFIG_OPENSSL |
|
|
|
|
if (!SSL_CTX_load_verify_locations(c->ctx, buf, NULL)) |
|
|
|
|
av_log(h, AV_LOG_ERROR, "SSL_CTX_load_verify_locations %s\n", ERR_error_string(ERR_get_error(), NULL)); |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
if (!c->ca_file && av_find_info_tag(buf, sizeof(buf), "cafile", p)) |
|
|
|
|
c->ca_file = av_strdup(buf); |
|
|
|
|
|
|
|
|
|
if (av_find_info_tag(buf, sizeof(buf), "verify", p)) { |
|
|
|
|
if (!c->verify && av_find_info_tag(buf, sizeof(buf), "verify", p)) { |
|
|
|
|
char *endptr = NULL; |
|
|
|
|
verify = strtol(buf, &endptr, 10); |
|
|
|
|
c->verify = strtol(buf, &endptr, 10); |
|
|
|
|
if (buf == endptr) |
|
|
|
|
verify = 1; |
|
|
|
|
c->verify = 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
has_cert = av_find_info_tag(buf, sizeof(buf), "cert", p); |
|
|
|
@ -151,14 +164,11 @@ static void set_options(URLContext *h, const char *uri) |
|
|
|
|
} else if (has_cert ^ has_key) { |
|
|
|
|
av_log(h, AV_LOG_ERROR, "cert and key required\n"); |
|
|
|
|
} |
|
|
|
|
gnutls_certificate_set_verify_flags(c->cred, verify); |
|
|
|
|
#elif CONFIG_OPENSSL |
|
|
|
|
if (has_cert && !SSL_CTX_use_certificate_chain_file(c->ctx, buf)) |
|
|
|
|
av_log(h, AV_LOG_ERROR, "SSL_CTX_use_certificate_chain_file %s\n", ERR_error_string(ERR_get_error(), NULL)); |
|
|
|
|
if (has_key && !SSL_CTX_use_PrivateKey_file(c->ctx, key, SSL_FILETYPE_PEM)) |
|
|
|
|
av_log(h, AV_LOG_ERROR, "SSL_CTX_use_PrivateKey_file %s\n", ERR_error_string(ERR_get_error(), NULL)); |
|
|
|
|
if (verify) |
|
|
|
|
SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0); |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -215,6 +225,17 @@ static int tls_open(URLContext *h, const char *uri, int flags) |
|
|
|
|
gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, host, strlen(host)); |
|
|
|
|
gnutls_certificate_allocate_credentials(&c->cred); |
|
|
|
|
set_options(h, uri); |
|
|
|
|
if (c->ca_file) { |
|
|
|
|
ret = gnutls_certificate_set_x509_trust_file(c->cred, c->ca_file, GNUTLS_X509_FMT_PEM); |
|
|
|
|
if (ret < 0) |
|
|
|
|
av_log(h, AV_LOG_ERROR, "%s\n", gnutls_strerror(ret)); |
|
|
|
|
} |
|
|
|
|
#if GNUTLS_VERSION_MAJOR >= 3 |
|
|
|
|
else |
|
|
|
|
gnutls_certificate_set_x509_system_trust(c->cred); |
|
|
|
|
#endif |
|
|
|
|
gnutls_certificate_set_verify_flags(c->cred, c->verify ? |
|
|
|
|
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT : 0); |
|
|
|
|
gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred); |
|
|
|
|
gnutls_transport_set_ptr(c->session, (gnutls_transport_ptr_t) |
|
|
|
|
(intptr_t) c->fd); |
|
|
|
@ -226,6 +247,38 @@ static int tls_open(URLContext *h, const char *uri, int flags) |
|
|
|
|
if ((ret = do_tls_poll(h, ret)) < 0) |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
if (c->verify) { |
|
|
|
|
unsigned int status, cert_list_size; |
|
|
|
|
gnutls_x509_crt_t cert; |
|
|
|
|
const gnutls_datum_t *cert_list; |
|
|
|
|
if ((ret = gnutls_certificate_verify_peers2(c->session, &status)) < 0) { |
|
|
|
|
av_log(h, AV_LOG_ERROR, "Unable to verify peer certificate: %s\n", |
|
|
|
|
gnutls_strerror(ret)); |
|
|
|
|
ret = AVERROR(EIO); |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
if (status & GNUTLS_CERT_INVALID) { |
|
|
|
|
av_log(h, AV_LOG_ERROR, "Peer certificate failed verification\n"); |
|
|
|
|
ret = AVERROR(EIO); |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
if (gnutls_certificate_type_get(c->session) != GNUTLS_CRT_X509) { |
|
|
|
|
av_log(h, AV_LOG_ERROR, "Unsupported certificate type\n"); |
|
|
|
|
ret = AVERROR(EIO); |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
gnutls_x509_crt_init(&cert); |
|
|
|
|
cert_list = gnutls_certificate_get_peers(c->session, &cert_list_size); |
|
|
|
|
gnutls_x509_crt_import(cert, cert_list, GNUTLS_X509_FMT_DER); |
|
|
|
|
ret = gnutls_x509_crt_check_hostname(cert, host); |
|
|
|
|
gnutls_x509_crt_deinit(cert); |
|
|
|
|
if (!ret) { |
|
|
|
|
av_log(h, AV_LOG_ERROR, |
|
|
|
|
"The certificate's owner does not match hostname %s\n", host); |
|
|
|
|
ret = AVERROR(EIO); |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
#elif CONFIG_OPENSSL |
|
|
|
|
c->ctx = SSL_CTX_new(server ? TLSv1_server_method() : TLSv1_client_method()); |
|
|
|
|
if (!c->ctx) { |
|
|
|
@ -234,6 +287,14 @@ static int tls_open(URLContext *h, const char *uri, int flags) |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
set_options(h, uri); |
|
|
|
|
if (c->ca_file) { |
|
|
|
|
if (!SSL_CTX_load_verify_locations(c->ctx, c->ca_file, NULL)) |
|
|
|
|
av_log(h, AV_LOG_ERROR, "SSL_CTX_load_verify_locations %s\n", ERR_error_string(ERR_get_error(), NULL)); |
|
|
|
|
} |
|
|
|
|
// Note, this doesn't check that the peer certificate actually matches
|
|
|
|
|
// the requested hostname.
|
|
|
|
|
if (c->verify) |
|
|
|
|
SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); |
|
|
|
|
c->ssl = SSL_new(c->ctx); |
|
|
|
|
if (!c->ssl) { |
|
|
|
|
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL)); |
|
|
|
@ -313,4 +374,5 @@ URLProtocol ff_tls_protocol = { |
|
|
|
|
.url_close = tls_close, |
|
|
|
|
.priv_data_size = sizeof(TLSContext), |
|
|
|
|
.flags = URL_PROTOCOL_FLAG_NETWORK, |
|
|
|
|
.priv_data_class = &tls_class, |
|
|
|
|
}; |
|
|
|
|