Merge commit '8b09d917e7dc7d7f2ace31419f802d4ff518236c'
authorMichael Niedermayer <michaelni@gmx.at>
Fri, 27 Sep 2013 07:22:42 +0000 (09:22 +0200)
committerMichael Niedermayer <michaelni@gmx.at>
Fri, 27 Sep 2013 07:33:28 +0000 (09:33 +0200)
* commit '8b09d917e7dc7d7f2ace31419f802d4ff518236c':
  tls: Add options for verifying the peer certificate

Conflicts:
doc/protocols.texi
libavformat/tls.c
libavformat/version.h

See: b2460858f64b2070d84dd861d4bbd16acfb9b0e9
See: 973a758f52a4e5fe63bd88806a4b2db034a032de
Merged-by: Michael Niedermayer <michaelni@gmx.at>
1  2 
doc/protocols.texi
libavformat/tls.c
libavformat/version.h

@@@ -853,46 -570,37 +853,61 @@@ ffplay tcp://@var{hostname}:@var{port
  
  @section tls
  
- Transport Layer Security/Secure Sockets Layer
+ Transport Layer Security (TLS) / Secure Sockets Layer (SSL)
  
 -The required syntax for a TLS url is:
 +The required syntax for a TLS/SSL url is:
  @example
 -tls://@var{hostname}:@var{port}
 +tls://@var{hostname}:@var{port}[?@var{options}]
  @end example
  
+ The following parameters can be set via command line options
+ (or in code via @code{AVOption}s):
  @table @option
  
 -@item ca_file
++@item ca_file, cafile=@var{filename}
+ 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.
++The file must be in OpenSSL PEM format.
+ @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.
 +@item listen
 +Act as a server, listening for an incoming connection.
 +
- @item cafile=@var{filename}
- Certificate authority file. The file must be in OpenSSL PEM format.
 +@item cert=@var{filename}
 +Certificate file. The file must be in OpenSSL PEM format.
 +
 +@item key=@var{filename}
 +Private key file.
 +
- @item verify=@var{0|1}
- Verify the peer's certificate.
  @end table
  
 +Example command lines:
 +
 +To create a TLS/SSL server that serves an input stream.
 +
 +@example
 +ffmpeg -i @var{input} -f @var{format} tls://@var{hostname}:@var{port}?listen&cert=@var{server.crt}&key=@var{server.key}
 +@end example
 +
 +To play back a stream from the TLS/SSL server using @command{ffplay}:
 +
 +@example
 +ffplay tls://@var{hostname}:@var{port}
 +@end example
 +
  @section udp
  
  User Datagram Protocol.
  #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 -67,26 +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;
      return 0;
  }
  
-     int has_cert, has_key, verify = 0;
 +static void set_options(URLContext *h, const char *uri)
 +{
 +    TLSContext *c = h->priv_data;
 +    char buf[1024], key[1024];
-     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
-     }
++    int has_cert, has_key;
 +#if CONFIG_GNUTLS
 +    int ret;
 +#endif
 +    const char *p = strchr(uri, '?');
 +    if (!p)
 +        return;
 +
-     if (av_find_info_tag(buf, sizeof(buf), "verify", p)) {
++    if (!c->ca_file && av_find_info_tag(buf, sizeof(buf), "cafile", p))
++        c->ca_file = av_strdup(buf);
 +
-         verify = strtol(buf, &endptr, 10);
++    if (!c->verify && av_find_info_tag(buf, sizeof(buf), "verify", p)) {
 +        char *endptr = NULL;
-             verify = 1;
++        c->verify = strtol(buf, &endptr, 10);
 +        if (buf == endptr)
-     gnutls_certificate_set_verify_flags(c->cred, verify);
++            c->verify = 1;
 +    }
 +
 +    has_cert = av_find_info_tag(buf, sizeof(buf), "cert", p);
 +    has_key  = av_find_info_tag(key, sizeof(key), "key", p);
 +#if CONFIG_GNUTLS
 +    if (has_cert && has_key) {
 +        ret = gnutls_certificate_set_x509_key_file(c->cred, buf, key, GNUTLS_X509_FMT_PEM);
 +        if (ret < 0)
 +            av_log(h, AV_LOG_ERROR, "%s\n", gnutls_strerror(ret));
 +    } else if (has_cert ^ has_key) {
 +        av_log(h, AV_LOG_ERROR, "cert and key required\n");
 +    }
-     if (verify)
-         SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
 +#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));
 +#endif
 +}
 +
  static int tls_open(URLContext *h, const char *uri, int flags)
  {
      TLSContext *c = h->priv_data;
      if (!numerichost)
          gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, host, strlen(host));
      gnutls_certificate_allocate_credentials(&c->cred);
 -    if (c->ca_file)
 -        gnutls_certificate_set_x509_trust_file(c->cred, c->ca_file, GNUTLS_X509_FMT_PEM);
 +    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);
          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());
 +    c->ctx = SSL_CTX_new(server ? TLSv1_server_method() : TLSv1_client_method());
      if (!c->ctx) {
          av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
          ret = AVERROR(EIO);
          goto fail;
      }
 -    if (c->ca_file)
 -        SSL_CTX_load_verify_locations(c->ctx, c->ca_file, NULL);
 +    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, NULL);
++        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));
@@@ -30,8 -30,8 +30,8 @@@
  #include "libavutil/avutil.h"
  
  #define LIBAVFORMAT_VERSION_MAJOR 55
 -#define LIBAVFORMAT_VERSION_MINOR  5
 -#define LIBAVFORMAT_VERSION_MICRO  2
 +#define LIBAVFORMAT_VERSION_MINOR 18
- #define LIBAVFORMAT_VERSION_MICRO 102
++#define LIBAVFORMAT_VERSION_MICRO 103
  
  #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                 LIBAVFORMAT_VERSION_MINOR, \