]> git.sesse.net Git - ffmpeg/commitdiff
tls: Add options for verifying the peer certificate
authorMartin Storsjö <martin@martin.st>
Tue, 22 Feb 2011 10:02:01 +0000 (12:02 +0200)
committerMartin Storsjö <martin@martin.st>
Thu, 26 Sep 2013 20:13:06 +0000 (23:13 +0300)
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.

Signed-off-by: Martin Storsjö <martin@martin.st>
doc/protocols.texi
libavformat/tls.c
libavformat/version.h

index 1c7de475f4228b80dacb7443408249b63346bacc..2c618b8e0e7a50898ac65dba4addf5497d9c1f9d 100644 (file)
@@ -568,6 +568,39 @@ avplay tcp://@var{hostname}:@var{port}
 
 @end table
 
+@section tls
+
+Transport Layer Security (TLS) / Secure Sockets Layer (SSL)
+
+The required syntax for a TLS url is:
+@example
+tls://@var{hostname}:@var{port}
+@end example
+
+The following parameters can be set via command line options
+(or in code via @code{AVOption}s):
+
+@table @option
+
+@item ca_file
+A file containing certificate authority (CA) root certificates to treat
+as trusted. If the linked TLS library contains a default this might not
+need to be specified for verification to work, but not all libraries and
+setups have defaults built in.
+
+@item tls_verify=@var{1|0}
+If enabled, try to verify the peer that we are communicating with.
+Note, if using OpenSSL, this currently only makes sure that the
+peer certificate is signed by one of the root certificates in the CA
+database, but it does not validate that the certificate actually
+matches the host name we are trying to connect to. (With GnuTLS,
+the host name is validated as well.)
+
+This is disabled by default since it requires a CA database to be
+provided by the caller in many cases.
+
+@end table
+
 @section udp
 
 User Datagram Protocol.
index 7fa6fc2caeb86164aa671ea0bbd8392601551eee..4f475e069c372b3da0ab3b8811349f1a7a573b9d 100644 (file)
 #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;
@@ -158,7 +178,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);
@@ -170,6 +197,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(TLSv1_client_method());
     if (!c->ctx) {
@@ -177,6 +236,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));
@@ -256,4 +321,5 @@ URLProtocol ff_tls_protocol = {
     .url_close      = tls_close,
     .priv_data_size = sizeof(TLSContext),
     .flags          = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class = &tls_class,
 };
index 6d8bd52f628525fb07a58cbee3b5ca5d48af6de6..66ca26403ac3b247010396da5e3f6c8d2eb086c0 100644 (file)
@@ -31,7 +31,7 @@
 
 #define LIBAVFORMAT_VERSION_MAJOR 55
 #define LIBAVFORMAT_VERSION_MINOR  5
-#define LIBAVFORMAT_VERSION_MICRO  1
+#define LIBAVFORMAT_VERSION_MICRO  2
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \