mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-23 12:43:46 +02:00
84ad471fce
Print every error in the stack, if more than one, and don't print bogus errors if there's none logged within OpenSSL. Retain the underlying IO error code, print an error message out of it, and pass the error code on to the caller. Signed-off-by: Martin Storsjö <martin@martin.st>
401 lines
11 KiB
C
401 lines
11 KiB
C
/*
|
|
* TLS/SSL Protocol
|
|
* Copyright (c) 2011 Martin Storsjo
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "avformat.h"
|
|
#include "internal.h"
|
|
#include "network.h"
|
|
#include "os_support.h"
|
|
#include "url.h"
|
|
#include "tls.h"
|
|
#include "libavcodec/internal.h"
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/avutil.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/parseutils.h"
|
|
#include "libavutil/thread.h"
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
|
|
static int openssl_init;
|
|
|
|
typedef struct TLSContext {
|
|
const AVClass *class;
|
|
TLSShared tls_shared;
|
|
SSL_CTX *ctx;
|
|
SSL *ssl;
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
|
|
BIO_METHOD* url_bio_method;
|
|
#endif
|
|
int io_err;
|
|
} TLSContext;
|
|
|
|
#if HAVE_THREADS && OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
#include <openssl/crypto.h>
|
|
pthread_mutex_t *openssl_mutexes;
|
|
static void openssl_lock(int mode, int type, const char *file, int line)
|
|
{
|
|
if (mode & CRYPTO_LOCK)
|
|
pthread_mutex_lock(&openssl_mutexes[type]);
|
|
else
|
|
pthread_mutex_unlock(&openssl_mutexes[type]);
|
|
}
|
|
#if !defined(WIN32) && OPENSSL_VERSION_NUMBER < 0x10000000
|
|
static unsigned long openssl_thread_id(void)
|
|
{
|
|
return (intptr_t) pthread_self();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
int ff_openssl_init(void)
|
|
{
|
|
ff_lock_avformat();
|
|
if (!openssl_init) {
|
|
/* OpenSSL 1.0.2 or below, then you would use SSL_library_init. If you are
|
|
* using OpenSSL 1.1.0 or above, then the library will initialize
|
|
* itself automatically.
|
|
* https://wiki.openssl.org/index.php/Library_Initialization
|
|
*/
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
#endif
|
|
#if HAVE_THREADS && OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
if (!CRYPTO_get_locking_callback()) {
|
|
int i;
|
|
openssl_mutexes = av_malloc_array(sizeof(pthread_mutex_t), CRYPTO_num_locks());
|
|
if (!openssl_mutexes) {
|
|
ff_unlock_avformat();
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
|
|
for (i = 0; i < CRYPTO_num_locks(); i++)
|
|
pthread_mutex_init(&openssl_mutexes[i], NULL);
|
|
CRYPTO_set_locking_callback(openssl_lock);
|
|
#if !defined(WIN32) && OPENSSL_VERSION_NUMBER < 0x10000000
|
|
CRYPTO_set_id_callback(openssl_thread_id);
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
openssl_init++;
|
|
ff_unlock_avformat();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ff_openssl_deinit(void)
|
|
{
|
|
ff_lock_avformat();
|
|
openssl_init--;
|
|
if (!openssl_init) {
|
|
#if HAVE_THREADS && OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
if (CRYPTO_get_locking_callback() == openssl_lock) {
|
|
int i;
|
|
CRYPTO_set_locking_callback(NULL);
|
|
for (i = 0; i < CRYPTO_num_locks(); i++)
|
|
pthread_mutex_destroy(&openssl_mutexes[i]);
|
|
av_free(openssl_mutexes);
|
|
}
|
|
#endif
|
|
}
|
|
ff_unlock_avformat();
|
|
}
|
|
|
|
static int print_tls_error(URLContext *h, int ret)
|
|
{
|
|
TLSContext *c = h->priv_data;
|
|
int printed = 0, e, averr = AVERROR(EIO);
|
|
if (h->flags & AVIO_FLAG_NONBLOCK) {
|
|
int err = SSL_get_error(c->ssl, ret);
|
|
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
|
|
return AVERROR(EAGAIN);
|
|
}
|
|
while ((e = ERR_get_error()) != 0) {
|
|
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(e, NULL));
|
|
printed = 1;
|
|
}
|
|
if (c->io_err) {
|
|
av_log(h, AV_LOG_ERROR, "IO error: %s\n", av_err2str(c->io_err));
|
|
printed = 1;
|
|
averr = c->io_err;
|
|
c->io_err = 0;
|
|
}
|
|
if (!printed)
|
|
av_log(h, AV_LOG_ERROR, "Unknown error\n");
|
|
return averr;
|
|
}
|
|
|
|
static int tls_close(URLContext *h)
|
|
{
|
|
TLSContext *c = h->priv_data;
|
|
if (c->ssl) {
|
|
SSL_shutdown(c->ssl);
|
|
SSL_free(c->ssl);
|
|
}
|
|
if (c->ctx)
|
|
SSL_CTX_free(c->ctx);
|
|
ffurl_closep(&c->tls_shared.tcp);
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
|
|
if (c->url_bio_method)
|
|
BIO_meth_free(c->url_bio_method);
|
|
#endif
|
|
ff_openssl_deinit();
|
|
return 0;
|
|
}
|
|
|
|
static int url_bio_create(BIO *b)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
|
|
BIO_set_init(b, 1);
|
|
BIO_set_data(b, NULL);
|
|
BIO_set_flags(b, 0);
|
|
#else
|
|
b->init = 1;
|
|
b->ptr = NULL;
|
|
b->flags = 0;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int url_bio_destroy(BIO *b)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
|
|
#define GET_BIO_DATA(x) BIO_get_data(x)
|
|
#else
|
|
#define GET_BIO_DATA(x) (x)->ptr
|
|
#endif
|
|
|
|
static int url_bio_bread(BIO *b, char *buf, int len)
|
|
{
|
|
TLSContext *c = GET_BIO_DATA(b);
|
|
int ret = ffurl_read(c->tls_shared.tcp, buf, len);
|
|
if (ret >= 0)
|
|
return ret;
|
|
BIO_clear_retry_flags(b);
|
|
if (ret == AVERROR_EXIT)
|
|
return 0;
|
|
if (ret == AVERROR(EAGAIN))
|
|
BIO_set_retry_read(b);
|
|
else
|
|
c->io_err = ret;
|
|
return -1;
|
|
}
|
|
|
|
static int url_bio_bwrite(BIO *b, const char *buf, int len)
|
|
{
|
|
TLSContext *c = GET_BIO_DATA(b);
|
|
int ret = ffurl_write(c->tls_shared.tcp, buf, len);
|
|
if (ret >= 0)
|
|
return ret;
|
|
BIO_clear_retry_flags(b);
|
|
if (ret == AVERROR_EXIT)
|
|
return 0;
|
|
if (ret == AVERROR(EAGAIN))
|
|
BIO_set_retry_write(b);
|
|
else
|
|
c->io_err = ret;
|
|
return -1;
|
|
}
|
|
|
|
static long url_bio_ctrl(BIO *b, int cmd, long num, void *ptr)
|
|
{
|
|
if (cmd == BIO_CTRL_FLUSH) {
|
|
BIO_clear_retry_flags(b);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int url_bio_bputs(BIO *b, const char *str)
|
|
{
|
|
return url_bio_bwrite(b, str, strlen(str));
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
|
|
static BIO_METHOD url_bio_method = {
|
|
.type = BIO_TYPE_SOURCE_SINK,
|
|
.name = "urlprotocol bio",
|
|
.bwrite = url_bio_bwrite,
|
|
.bread = url_bio_bread,
|
|
.bputs = url_bio_bputs,
|
|
.bgets = NULL,
|
|
.ctrl = url_bio_ctrl,
|
|
.create = url_bio_create,
|
|
.destroy = url_bio_destroy,
|
|
};
|
|
#endif
|
|
|
|
static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
|
|
{
|
|
TLSContext *p = h->priv_data;
|
|
TLSShared *c = &p->tls_shared;
|
|
BIO *bio;
|
|
int ret;
|
|
|
|
if ((ret = ff_openssl_init()) < 0)
|
|
return ret;
|
|
|
|
if ((ret = ff_tls_open_underlying(c, h, uri, options)) < 0)
|
|
goto fail;
|
|
|
|
// We want to support all versions of TLS >= 1.0, but not the deprecated
|
|
// and insecure SSLv2 and SSLv3. Despite the name, SSLv23_*_method()
|
|
// enables support for all versions of SSL and TLS, and we then disable
|
|
// support for the old protocols immediately after creating the context.
|
|
p->ctx = SSL_CTX_new(c->listen ? SSLv23_server_method() : SSLv23_client_method());
|
|
if (!p->ctx) {
|
|
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
|
|
ret = AVERROR(EIO);
|
|
goto fail;
|
|
}
|
|
SSL_CTX_set_options(p->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
|
if (c->ca_file) {
|
|
if (!SSL_CTX_load_verify_locations(p->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));
|
|
}
|
|
if (c->cert_file && !SSL_CTX_use_certificate_chain_file(p->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(p->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
|
|
// the requested hostname.
|
|
if (c->verify)
|
|
SSL_CTX_set_verify(p->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
|
|
p->ssl = SSL_new(p->ctx);
|
|
if (!p->ssl) {
|
|
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
|
|
ret = AVERROR(EIO);
|
|
goto fail;
|
|
}
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
|
|
p->url_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "urlprotocol bio");
|
|
BIO_meth_set_write(p->url_bio_method, url_bio_bwrite);
|
|
BIO_meth_set_read(p->url_bio_method, url_bio_bread);
|
|
BIO_meth_set_puts(p->url_bio_method, url_bio_bputs);
|
|
BIO_meth_set_ctrl(p->url_bio_method, url_bio_ctrl);
|
|
BIO_meth_set_create(p->url_bio_method, url_bio_create);
|
|
BIO_meth_set_destroy(p->url_bio_method, url_bio_destroy);
|
|
bio = BIO_new(p->url_bio_method);
|
|
BIO_set_data(bio, p);
|
|
#else
|
|
bio = BIO_new(&url_bio_method);
|
|
bio->ptr = p;
|
|
#endif
|
|
SSL_set_bio(p->ssl, bio, bio);
|
|
if (!c->listen && !c->numerichost)
|
|
SSL_set_tlsext_host_name(p->ssl, c->host);
|
|
ret = c->listen ? SSL_accept(p->ssl) : SSL_connect(p->ssl);
|
|
if (ret == 0) {
|
|
av_log(h, AV_LOG_ERROR, "Unable to negotiate TLS/SSL session\n");
|
|
ret = AVERROR(EIO);
|
|
goto fail;
|
|
} else if (ret < 0) {
|
|
ret = print_tls_error(h, ret);
|
|
goto fail;
|
|
}
|
|
|
|
return 0;
|
|
fail:
|
|
tls_close(h);
|
|
return ret;
|
|
}
|
|
|
|
static int tls_read(URLContext *h, uint8_t *buf, int size)
|
|
{
|
|
TLSContext *c = h->priv_data;
|
|
int ret;
|
|
// Set or clear the AVIO_FLAG_NONBLOCK on c->tls_shared.tcp
|
|
c->tls_shared.tcp->flags &= ~AVIO_FLAG_NONBLOCK;
|
|
c->tls_shared.tcp->flags |= h->flags & AVIO_FLAG_NONBLOCK;
|
|
ret = SSL_read(c->ssl, buf, size);
|
|
if (ret > 0)
|
|
return ret;
|
|
if (ret == 0)
|
|
return AVERROR_EOF;
|
|
return print_tls_error(h, ret);
|
|
}
|
|
|
|
static int tls_write(URLContext *h, const uint8_t *buf, int size)
|
|
{
|
|
TLSContext *c = h->priv_data;
|
|
int ret;
|
|
// Set or clear the AVIO_FLAG_NONBLOCK on c->tls_shared.tcp
|
|
c->tls_shared.tcp->flags &= ~AVIO_FLAG_NONBLOCK;
|
|
c->tls_shared.tcp->flags |= h->flags & AVIO_FLAG_NONBLOCK;
|
|
ret = SSL_write(c->ssl, buf, size);
|
|
if (ret > 0)
|
|
return ret;
|
|
if (ret == 0)
|
|
return AVERROR_EOF;
|
|
return print_tls_error(h, ret);
|
|
}
|
|
|
|
static int tls_get_file_handle(URLContext *h)
|
|
{
|
|
TLSContext *c = h->priv_data;
|
|
return ffurl_get_file_handle(c->tls_shared.tcp);
|
|
}
|
|
|
|
static int tls_get_short_seek(URLContext *h)
|
|
{
|
|
TLSContext *s = h->priv_data;
|
|
return ffurl_get_short_seek(s->tls_shared.tcp);
|
|
}
|
|
|
|
static const AVOption options[] = {
|
|
TLS_COMMON_OPTIONS(TLSContext, tls_shared),
|
|
{ NULL }
|
|
};
|
|
|
|
static const AVClass tls_class = {
|
|
.class_name = "tls",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
};
|
|
|
|
const URLProtocol ff_tls_protocol = {
|
|
.name = "tls",
|
|
.url_open2 = tls_open,
|
|
.url_read = tls_read,
|
|
.url_write = tls_write,
|
|
.url_close = tls_close,
|
|
.url_get_file_handle = tls_get_file_handle,
|
|
.url_get_short_seek = tls_get_short_seek,
|
|
.priv_data_size = sizeof(TLSContext),
|
|
.flags = URL_PROTOCOL_FLAG_NETWORK,
|
|
.priv_data_class = &tls_class,
|
|
};
|