A file containing the trusted CA certificates needs to be
supplied via the ca_file AVOption, unless the TLS library
has got a system default file/database set up.

This doesn't check the hostname of the peer certificate with
openssl, which requires a non-trivial piece of code for
manually matching the desired hostname to the string provided
by the certificate, not provided as a library function.

That is, with openssl, this only validates that the received
certificate is signed with the right CA, but not that it is
the actual server we think we're talking to.

Verification is still disabled by default since we can't count
on a proper CA database existing at all times.
---
Added support for gnutls_certificate_set_x509_system_trust and
some other minor fixes.
---
 libavformat/tls.c |   70 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 69 insertions(+), 1 deletion(-)

diff --git a/libavformat/tls.c b/libavformat/tls.c
index fecf096..55b7416 100644
--- a/libavformat/tls.c
+++ b/libavformat/tls.c
@@ -22,8 +22,10 @@
 #include "avformat.h"
 #include "url.h"
 #include "libavutil/avstring.h"
+#include "libavutil/opt.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)
@@ -65,8 +67,26 @@ 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 },
+    {"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;
@@ -108,6 +128,11 @@ static int tls_open(URLContext *h, const char *uri, int 
flags)
     TLSContext *c = h->priv_data;
     int ret;
     int port;
+#if CONFIG_GNUTLS
+    unsigned int status, cert_list_size;
+    gnutls_x509_crt_t cert;
+    const gnutls_datum_t *cert_list;
+#endif
     char buf[200], host[200];
     int numerichost = 0;
     struct addrinfo hints = { 0 }, *ai = NULL;
@@ -151,7 +176,14 @@ static int tls_open(URLContext *h, const char *uri, int 
flags)
     if (!numerichost)
         gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, host, 
strlen(host));
     gnutls_certificate_allocate_credentials(&c->cred);
-    gnutls_certificate_set_verify_flags(c->cred, 0);
+    if (c->ca_file)
+        gnutls_certificate_set_x509_trust_file(c->cred, c->ca_file, 
GNUTLS_X509_FMT_PEM);
+#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);
@@ -163,6 +195,35 @@ static int tls_open(URLContext *h, const char *uri, int 
flags)
         if ((ret = do_tls_poll(h, ret)) < 0)
             goto fail;
     }
+    if (c->verify) {
+        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(TLSv1_client_method());
     if (!c->ctx) {
@@ -170,6 +231,12 @@ static int tls_open(URLContext *h, const char *uri, int 
flags)
         ret = AVERROR(EIO);
         goto fail;
     }
+    if (c->ca_file)
+        SSL_CTX_load_verify_locations(c->ctx, c->ca_file, 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, 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));
@@ -249,4 +316,5 @@ URLProtocol ff_tls_protocol = {
     .url_close      = tls_close,
     .priv_data_size = sizeof(TLSContext),
     .flags          = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class = &tls_class,
 };
-- 
1.7.9.4

_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to