]> git.sesse.net Git - vlc/blobdiff - modules/misc/gnutls.c
Qt: translate wizard buttons (fix #13753)
[vlc] / modules / misc / gnutls.c
index 3cbacc6fef5b582fe4baaad6d8feb01c2eda8041..c76061ee385da89481b133c9d58b03c251ea3cd7 100644 (file)
 /*****************************************************************************
  * gnutls.c
  *****************************************************************************
- * Copyright (C) 2004-2011 Rémi Denis-Courmont
+ * Copyright (C) 2004-2014 Rémi Denis-Courmont
  *
  * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * 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.
  *
  * This program 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-/*****************************************************************************
- * Preamble
+ * You should have received a copy of the GNU Öesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
 #ifdef HAVE_CONFIG_H
 # include "config.h"
 #endif
 
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
 #include <errno.h>
-#include <sys/types.h>
-#include <errno.h>
-
-#include <sys/stat.h>
-#ifdef WIN32
-# include <windows.h>
-# include <io.h>
-# include <wincrypt.h>
-#else
-# include <unistd.h>
-#endif
-#include <fcntl.h>
+#include <assert.h>
 
 #include <vlc_common.h>
 #include <vlc_plugin.h>
 #include <vlc_tls.h>
-#include <vlc_charset.h>
-#include <vlc_fs.h>
 #include <vlc_block.h>
+#include <vlc_dialog.h>
 
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
-
 #include "dhparams.h"
 
-#include <assert.h>
-
-/*****************************************************************************
- * Module descriptor
- *****************************************************************************/
-static int  OpenClient  (vlc_tls_t *, int, const char *);
-static void CloseClient (vlc_tls_t *);
-static int  OpenServer  (vlc_tls_creds_t *, const char *, const char *);
-static void CloseServer (vlc_tls_creds_t *);
-
-#define PRIORITIES_TEXT N_("TLS cipher priorities")
-#define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
-    "hash functions and compression methods can be selected. " \
-    "Refer to GNU TLS documentation for detailed syntax.")
-static const char *const priorities_values[] = {
-    "PERFORMANCE",
-    "NORMAL",
-    "SECURE128",
-    "SECURE256",
-    "EXPORT",
-};
-static const char *const priorities_text[] = {
-    N_("Performance (prioritize faster ciphers)"),
-    N_("Normal"),
-    N_("Secure 128-bits (exclude 256-bits ciphers)"),
-    N_("Secure 256-bits (prioritize 256-bits ciphers)"),
-    N_("Export (include insecure ciphers)"),
-};
-
-vlc_module_begin ()
-    set_shortname( "GNU TLS" )
-    set_description( N_("GNU TLS transport layer security") )
-    set_capability( "tls client", 1 )
-    set_callbacks( OpenClient, CloseClient )
-    set_category( CAT_ADVANCED )
-    set_subcategory( SUBCAT_ADVANCED_MISC )
-
-    add_submodule ()
-        set_description( N_("GNU TLS server") )
-        set_capability( "tls server", 1 )
-        set_category( CAT_ADVANCED )
-        set_subcategory( SUBCAT_ADVANCED_MISC )
-        set_callbacks( OpenServer, CloseServer )
-
-        add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
-                    PRIORITIES_LONGTEXT, false)
-            change_string_list (priorities_values, priorities_text)
-vlc_module_end ()
+#if (GNUTLS_VERSION_NUMBER >= 0x030300)
+static int gnutls_Init (vlc_object_t *obj)
+{
+    const char *version = gnutls_check_version ("3.3.0");
+    if (version == NULL)
+    {
+        msg_Err (obj, "unsupported GnuTLS version");
+        return -1;
+    }
+    msg_Dbg (obj, "using GnuTLS version %s", version);
+    return 0;
+}
 
+# define gnutls_Deinit() (void)0
+#else
 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
 
 /**
  * Initializes GnuTLS with proper locking.
  * @return VLC_SUCCESS on success, a VLC error code otherwise.
  */
-static int gnutls_Init (vlc_object_t *p_this)
+static int gnutls_Init (vlc_object_t *obj)
 {
-    int ret = VLC_EGENERIC;
-
-    vlc_mutex_lock (&gnutls_mutex);
-    if (gnutls_global_init ())
+    const char *version = gnutls_check_version ("3.1.11");
+    if (version == NULL)
     {
-        msg_Err (p_this, "cannot initialize GnuTLS");
-        goto error;
+        msg_Err (obj, "unsupported GnuTLS version");
+        return -1;
     }
+    msg_Dbg (obj, "using GnuTLS version %s", version);
 
-    const char *psz_version = gnutls_check_version ("2.6.6");
-    if (psz_version == NULL)
+    if (gnutls_check_version ("3.3.0") == NULL)
     {
-        msg_Err (p_this, "unsupported GnuTLS version");
-        gnutls_global_deinit ();
-        goto error;
-    }
+         int val;
 
-    msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
-    ret = VLC_SUCCESS;
+         vlc_mutex_lock (&gnutls_mutex);
+         val = gnutls_global_init ();
+         vlc_mutex_unlock (&gnutls_mutex);
 
-error:
-    vlc_mutex_unlock (&gnutls_mutex);
-    return ret;
+         if (val)
+         {
+             msg_Err (obj, "cannot initialize GnuTLS");
+             return -1;
+         }
+    }
+    return 0;
 }
 
-
 /**
  * Deinitializes GnuTLS.
  */
-static void gnutls_Deinit (vlc_object_t *p_this)
+static void gnutls_Deinit (void)
 {
     vlc_mutex_lock (&gnutls_mutex);
-
     gnutls_global_deinit ();
-    msg_Dbg (p_this, "GnuTLS deinitialized");
     vlc_mutex_unlock (&gnutls_mutex);
 }
-
+#endif
 
 static int gnutls_Error (vlc_object_t *obj, int val)
 {
     switch (val)
     {
         case GNUTLS_E_AGAIN:
-#ifdef WIN32
+#ifdef _WIN32
             WSASetLastError (WSAEWOULDBLOCK);
 #else
             errno = EAGAIN;
@@ -161,7 +110,7 @@ static int gnutls_Error (vlc_object_t *obj, int val)
             break;
 
         case GNUTLS_E_INTERRUPTED:
-#ifdef WIN32
+#ifdef _WIN32
             WSASetLastError (WSAEINTR);
 #else
             errno = EINTR;
@@ -174,7 +123,7 @@ static int gnutls_Error (vlc_object_t *obj, int val)
             if (!gnutls_error_is_fatal (val))
                 msg_Err (obj, "Error above should be handled");
 #endif
-#ifdef WIN32
+#ifdef _WIN32
             WSASetLastError (WSAECONNRESET);
 #else
             errno = ECONNRESET;
@@ -184,25 +133,18 @@ static int gnutls_Error (vlc_object_t *obj, int val)
 }
 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
 
-struct vlc_tls_sys
-{
-    gnutls_session_t session;
-    gnutls_certificate_credentials_t x509_cred;
-    char *hostname;
-    bool handshaked;
-};
-
-
 /**
  * Sends data through a TLS session.
  */
 static int gnutls_Send (void *opaque, const void *buf, size_t length)
 {
-    vlc_tls_t *session = opaque;
-    vlc_tls_sys_t *sys = session->sys;
+    assert (opaque != NULL);
+
+    vlc_tls_t *tls = opaque;
+    gnutls_session_t session = tls->sys;
 
-    int val = gnutls_record_send (sys->session, buf, length);
-    return (val < 0) ? gnutls_Error (session, val) : val;
+    int val = gnutls_record_send (session, buf, length);
+    return (val < 0) ? gnutls_Error (tls, val) : val;
 }
 
 
@@ -211,567 +153,389 @@ static int gnutls_Send (void *opaque, const void *buf, size_t length)
  */
 static int gnutls_Recv (void *opaque, void *buf, size_t length)
 {
-    vlc_tls_t *session = opaque;
-    vlc_tls_sys_t *sys = session->sys;
+    assert (opaque != NULL);
 
-    int val = gnutls_record_recv (sys->session, buf, length);
-    return (val < 0) ? gnutls_Error (session, val) : val;
-}
+    vlc_tls_t *tls = opaque;
+    gnutls_session_t session = tls->sys;
 
+    int val = gnutls_record_recv (session, buf, length);
+    return (val < 0) ? gnutls_Error (tls, val) : val;
+}
 
-/**
- * Starts or continues the TLS handshake.
- *
- * @return -1 on fatal error, 0 on successful handshake completion,
- * 1 if more would-be blocking recv is needed,
- * 2 if more would-be blocking send is required.
- */
-static int gnutls_ContinueHandshake (vlc_tls_t *session)
+static int gnutls_SessionOpen (vlc_tls_t *tls, int type,
+                               gnutls_certificate_credentials_t x509, int fd,
+                               const char *const *alpn)
 {
-    vlc_tls_sys_t *sys = session->sys;
+    gnutls_session_t session;
+    const char *errp;
     int val;
 
-#ifdef WIN32
-    WSASetLastError (0);
-#endif
-    val = gnutls_handshake (sys->session);
-    if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
-        return 1 + gnutls_record_get_direction (sys->session);
-
-    if (val < 0)
+    val = gnutls_init (&session, type);
+    if (val != 0)
     {
-#ifdef WIN32
-        msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
-#endif
-        msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
-        return -1;
+        msg_Err (tls, "cannot initialize TLS session: %s",
+                 gnutls_strerror (val));
+        return VLC_EGENERIC;
     }
 
-    sys->handshaked = true;
-    return 0;
-}
-
-
-typedef struct
-{
-    int flag;
-    const char *msg;
-} error_msg_t;
-
-static const error_msg_t cert_errors[] =
-{
-    { GNUTLS_CERT_INVALID,
-        "Certificate could not be verified" },
-    { GNUTLS_CERT_REVOKED,
-        "Certificate was revoked" },
-    { GNUTLS_CERT_SIGNER_NOT_FOUND,
-        "Certificate's signer was not found" },
-    { GNUTLS_CERT_SIGNER_NOT_CA,
-        "Certificate's signer is not a CA" },
-    { GNUTLS_CERT_INSECURE_ALGORITHM,
-        "Insecure certificate signature algorithm" },
-    { GNUTLS_CERT_NOT_ACTIVATED,
-        "Certificate is not yet activated" },
-    { GNUTLS_CERT_EXPIRED,
-        "Certificate has expired" },
-    { 0, NULL }
-};
-
-
-static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
-{
-    vlc_tls_sys_t *sys = session->sys;
-
-    int val = gnutls_ContinueHandshake (session);
-    if (val)
-        return val;
+    char *priorities = var_InheritString (tls, "gnutls-priorities");
+    if (unlikely(priorities == NULL))
+        goto error;
 
-    /* certificates chain verification */
-    unsigned status;
+    val = gnutls_priority_set_direct (session, priorities, &errp);
+    if (val < 0)
+        msg_Err (tls, "cannot set TLS priorities \"%s\": %s", errp,
+                 gnutls_strerror (val));
+    free (priorities);
+    if (val < 0)
+        goto error;
 
-    val = gnutls_certificate_verify_peers2 (sys->session, &status);
-    if (val)
+    val = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509);
+    if (val < 0)
     {
-        msg_Err (session, "Certificate verification failed: %s",
+        msg_Err (tls, "cannot set TLS session credentials: %s",
                  gnutls_strerror (val));
-        return -1;
+        goto error;
     }
 
-    if (status)
+    if (alpn != NULL)
     {
-        msg_Err (session, "TLS session: access denied (status 0x%X)", status);
-        for (const error_msg_t *e = cert_errors; e->flag; e++)
+        gnutls_datum_t *protv = NULL;
+        unsigned protc = 0;
+
+        while (*alpn != NULL)
         {
-            if (status & e->flag)
+            gnutls_datum_t *n = realloc(protv, sizeof (*protv) * (protc + 1));
+            if (unlikely(n == NULL))
             {
-                msg_Err (session, "%s", e->msg);
-                status &= ~e->flag;
+                free(protv);
+                goto error;
             }
-        }
-
-        if (status)
-            msg_Err (session,
-                     "unknown certificate error (you found a bug in VLC)");
-        return -1;
-    }
-
-    /* certificate (host)name verification */
-    const gnutls_datum_t *data;
-    data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
-    if (data == NULL)
-    {
-        msg_Err (session, "Peer certificate not available");
-        return -1;
-    }
+            protv = n;
 
-    gnutls_x509_crt_t cert;
-    val = gnutls_x509_crt_init (&cert);
-    if (val)
-    {
-        msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
-        return -1;
-    }
+            protv[protc].data = (void *)*alpn;
+            protv[protc].size = strlen(*alpn);
+            protc++;
+            alpn++;
+        }
 
-    val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
-    if (val)
-    {
-        msg_Err (session, "Certificate import error: %s",
-                 gnutls_strerror (val));
-        goto error;
+        val = gnutls_alpn_set_protocols (session, protv, protc, 0);
+        free (protv);
     }
 
-    if (sys->hostname != NULL
-     && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
-    {
-        msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
-        goto error;
-    }
+    gnutls_transport_set_int (session, fd);
 
-    gnutls_x509_crt_deinit (cert);
-    msg_Dbg (session, "TLS/X.509 certificate verified");
-    return 0;
+    tls->sys = session;
+    tls->sock.p_sys = NULL;
+    tls->sock.pf_send = gnutls_Send;
+    tls->sock.pf_recv = gnutls_Recv;
+    return VLC_SUCCESS;
 
 error:
-    gnutls_x509_crt_deinit (cert);
-    return -1;
-}
-
-static int
-gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
-{
-    char *priorities = var_InheritString (obj, "gnutls-priorities");
-    if (unlikely(priorities == NULL))
-        return VLC_ENOMEM;
-
-    const char *errp;
-    int val = gnutls_priority_set_direct (session, priorities, &errp);
-    if (val < 0)
-    {
-        msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
-                 gnutls_strerror (val));
-        val = VLC_EGENERIC;
-    }
-    else
-        val = VLC_SUCCESS;
-    free (priorities);
-    return val;
+    gnutls_deinit (session);
+    return VLC_EGENERIC;
 }
 
-#ifndef WIN32
 /**
- * Loads x509 credentials from a file descriptor (directory or regular file)
- * and closes the descriptor.
+ * Starts or continues the TLS handshake.
+ *
+ * @return -1 on fatal error, 0 on successful handshake completion,
+ * 1 if more would-be blocking recv is needed,
+ * 2 if more would-be blocking send is required.
  */
-static void gnutls_x509_AddFD (vlc_object_t *obj,
-                               gnutls_certificate_credentials_t cred,
-                               int fd, bool priv, unsigned recursion)
+static int gnutls_ContinueHandshake (vlc_tls_t *tls, char **restrict alp)
 {
-    DIR *dir = fdopendir (fd);
-    if (dir != NULL)
+    gnutls_session_t session = tls->sys;
+    int val;
+
+#ifdef _WIN32
+    WSASetLastError (0);
+#endif
+    do
     {
-        if (recursion == 0)
-            goto skipdir;
-        recursion--;
+        val = gnutls_handshake (session);
+        msg_Dbg (tls, "TLS handshake: %s", gnutls_strerror (val));
 
-        for (;;)
+        switch (val)
         {
-            char *ent = vlc_readdir (dir);
-            if (ent == NULL)
-                break;
-
-            if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
-            {
-                free (ent);
-                continue;
-            }
-
-            int nfd = vlc_openat (fd, ent, O_RDONLY);
-            if (nfd != -1)
-            {
-                msg_Dbg (obj, "loading x509 credentials from %s...", ent);
-                gnutls_x509_AddFD (obj, cred, nfd, priv, recursion);
-            }
-            else
-                msg_Dbg (obj, "cannot access x509 credentials in %s", ent);
-            free (ent);
+            case GNUTLS_E_SUCCESS:
+                goto done;
+            case GNUTLS_E_AGAIN:
+            case GNUTLS_E_INTERRUPTED:
+                /* I/O event: return to caller's poll() loop */
+                return 1 + gnutls_record_get_direction (session);
         }
-    skipdir:
-        closedir (dir);
-        return;
     }
+    while (!gnutls_error_is_fatal (val));
+
+#ifdef _WIN32
+    msg_Dbg (tls, "Winsock error %d", WSAGetLastError ());
+#endif
+    msg_Err (tls, "TLS handshake error: %s", gnutls_strerror (val));
+    return -1;
 
-    block_t *block = block_File (fd);
-    if (block != NULL)
+done:
+    if (alp != NULL)
     {
-        gnutls_datum_t data = {
-            .data = block->p_buffer,
-            .size = block->i_buffer,
-        };
-        int res = priv
-            ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
-                                                   GNUTLS_X509_FMT_PEM)
-            : gnutls_certificate_set_x509_trust_mem (cred, &data,
-                                                     GNUTLS_X509_FMT_PEM);
-        block_Release (block);
-
-        if (res < 0)
-            msg_Warn (obj, "cannot add x509 credentials: %s",
-                      gnutls_strerror (res));
+        gnutls_datum_t datum;
+
+        val = gnutls_alpn_get_selected_protocol (session, &datum);
+        if (val == 0)
+        {
+            if (memchr (datum.data, 0, datum.size) != NULL)
+                return -1; /* Other end is doing something fishy?! */
+
+            *alp = strndup ((char *)datum.data, datum.size);
+            if (unlikely(*alp == NULL))
+                return -1;
+        }
         else
-            msg_Dbg (obj, "added %d %s(s)", res, priv ? "key" : "certificate");
+            *alp = NULL;
     }
-    else
-        msg_Warn (obj, "cannot read x509 credentials: %m");
-    close (fd);
+    return 0;
 }
 
-static void gnutls_x509_AddPath (vlc_object_t *obj,
-                                 gnutls_certificate_credentials_t cred,
-                                 const char *path, bool priv)
+/**
+ * Terminates TLS session and releases session data.
+ * You still have to close the socket yourself.
+ */
+static void gnutls_SessionClose (vlc_tls_t *tls)
 {
-    msg_Dbg (obj, "loading x509 credentials in %s...", path);
-    int fd = vlc_open (path, O_RDONLY);
-    if (fd == -1)
-    {
-        msg_Warn (obj, "cannot access x509 in %s: %m", path);
-        return;
-    }
+    gnutls_session_t session = tls->sys;
 
-    gnutls_x509_AddFD (obj, cred, fd, priv, 5);
+    if (tls->sock.p_sys != NULL)
+        gnutls_bye (session, GNUTLS_SHUT_WR);
+
+    gnutls_deinit (session);
 }
-#else /* WIN32 */
-static int
-gnutls_loadOSCAList (vlc_object_t *p_this,
-                     gnutls_certificate_credentials cred)
+
+static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
+                                     int fd, const char *hostname,
+                                     const char *const *alpn)
 {
-    HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
-    if (!hCertStore)
-    {
-        msg_Warn (p_this, "could not open the Cert SystemStore");
-        return VLC_EGENERIC;
-    }
+    int val = gnutls_SessionOpen (tls, GNUTLS_CLIENT, crd->sys, fd, alpn);
+    if (val != VLC_SUCCESS)
+        return val;
 
-    PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
-    while( pCertContext )
-    {
-        gnutls_datum data = {
-            .data = pCertContext->pbCertEncoded,
-            .size = pCertContext->cbCertEncoded,
-        };
+    gnutls_session_t session = tls->sys;
 
-        if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
-        {
-            msg_Warn (p_this, "cannot add x509 credential");
-            return VLC_EGENERIC;
-        }
+    /* minimum DH prime bits */
+    gnutls_dh_set_prime_bits (session, 1024);
+
+    if (likely(hostname != NULL))
+        /* fill Server Name Indication */
+        gnutls_server_name_set (session, GNUTLS_NAME_DNS,
+                                hostname, strlen (hostname));
 
-        pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
-    }
     return VLC_SUCCESS;
 }
-#endif /* WIN32 */
 
-/**
- * Initializes a client-side TLS session.
- */
-static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
+static int gnutls_ClientHandshake (vlc_tls_t *tls, const char *host,
+                                   const char *service, char **restrict alp)
 {
-    if (gnutls_Init (VLC_OBJECT(session)))
-        return VLC_EGENERIC;
-
-    vlc_tls_sys_t *sys = malloc (sizeof (*sys));
-    if (unlikely(sys == NULL))
-    {
-        gnutls_Deinit (VLC_OBJECT(session));
-        return VLC_ENOMEM;
-    }
+    int val = gnutls_ContinueHandshake (tls, alp);
+    if (val)
+        return val;
 
-    session->sys = sys;
-    session->sock.p_sys = session;
-    session->sock.pf_send = gnutls_Send;
-    session->sock.pf_recv = gnutls_Recv;
-    sys->handshaked = false;
+    /* certificates chain verification */
+    gnutls_session_t session = tls->sys;
+    unsigned status;
 
-    int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
-    if (val != 0)
+    val = gnutls_certificate_verify_peers3 (session, host, &status);
+    if (val)
     {
-        msg_Err (session, "cannot allocate credentials: %s",
+        msg_Err (tls, "Certificate verification error: %s",
                  gnutls_strerror (val));
-        goto error;
-    }
-
-#ifndef WIN32
-    char *userdir = config_GetUserDir (VLC_DATA_DIR);
-    if (userdir != NULL)
-    {
-        char path[strlen (userdir) + sizeof ("/ssl/private/")];
-        sprintf (path, "%s/ssl", userdir);
-        vlc_mkdir (path, 0755);
-
-        sprintf (path, "%s/ssl/certs/", userdir);
-        gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
-        sprintf (path, "%s/ssl/private/", userdir);
-        gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, true);
-        free (userdir);
+failure:
+        gnutls_bye (session, GNUTLS_SHUT_RDWR);
+        return -1;
     }
 
-    const char *confdir = config_GetConfDir ();
-    {
-        char path[strlen (confdir)
-                   + sizeof ("/ssl/certs/ca-certificates.crt")];
-        sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
-        gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
+    if (status == 0)
+    {   /* Good certificate */
+success:
+        tls->sock.p_sys = tls;
+        return 0;
     }
-#else /* WIN32 */
-    gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
-#endif
-    gnutls_certificate_set_verify_flags (sys->x509_cred,
-                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
 
-    session->handshake = gnutls_HandshakeAndValidate;
-    /*session->_handshake = gnutls_ContinueHandshake;*/
+    /* Bad certificate */
+    gnutls_datum_t desc;
 
-    val = gnutls_init (&sys->session, GNUTLS_CLIENT);
-    if (val != 0)
+    if (gnutls_certificate_verification_status_print(status,
+                         gnutls_certificate_type_get (session), &desc, 0) == 0)
     {
-        msg_Err (session, "cannot initialize TLS session: %s",
-                 gnutls_strerror (val));
-        gnutls_certificate_free_credentials (sys->x509_cred);
-        goto error;
+        msg_Err (tls, "Certificate verification failure: %s", desc.data);
+        gnutls_free (desc.data);
     }
 
-    if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
-        goto s_error;
+    status &= ~GNUTLS_CERT_INVALID; /* always set / catch-all error */
+    status &= ~GNUTLS_CERT_SIGNER_NOT_FOUND; /* unknown CA */
+    status &= ~GNUTLS_CERT_UNEXPECTED_OWNER; /* mismatched hostname */
 
-    /* minimum DH prime bits */
-    gnutls_dh_set_prime_bits (sys->session, 1024);
+    if (status != 0 || host == NULL)
+        goto failure; /* Really bad certificate */
 
-    val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
-                                  sys->x509_cred);
-    if (val < 0)
+    /* Look up mismatching certificate in store */
+    const gnutls_datum_t *datum;
+    unsigned count;
+
+    datum = gnutls_certificate_get_peers (session, &count);
+    if (datum == NULL || count == 0)
     {
-        msg_Err (session, "cannot set TLS session credentials: %s",
-                 gnutls_strerror (val));
-        goto s_error;
+        msg_Err (tls, "Peer certificate not available");
+        goto failure;
     }
 
-    /* server name */
-    if (likely(hostname != NULL))
+    msg_Dbg (tls, "%u certificate(s) in the list", count);
+    val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
+                                       GNUTLS_CRT_X509, datum, 0);
+    const char *msg;
+    switch (val)
     {
-        /* fill Server Name Indication */
-        gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
-                                hostname, strlen (hostname));
-        /* keep hostname to match CNAME after handshake */
-        sys->hostname = strdup (hostname);
-        if (unlikely(sys->hostname == NULL))
-            goto s_error;
+        case 0:
+            msg_Dbg (tls, "certificate key match for %s", host);
+            goto success;
+        case GNUTLS_E_NO_CERTIFICATE_FOUND:
+            msg_Dbg (tls, "no known certificates for %s", host);
+            msg = N_("However the security certificate presented by the "
+                "server is unknown and could not be authenticated by any "
+                "trusted Certificate Authority.");
+            break;
+        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
+            msg_Dbg (tls, "certificate keys mismatch for %s", host);
+            msg = N_("However the security certificate presented by the "
+                "server changed since the previous visit and was not "
+                "authenticated by any trusted Certificate Authority. ");
+            break;
+        default:
+            msg_Err (tls, "certificate key match error for %s: %s", host,
+                     gnutls_strerror (val));
+            goto failure;
     }
-    else
-        sys->hostname = NULL;
 
-    gnutls_transport_set_ptr (sys->session,
-                              (gnutls_transport_ptr_t)(intptr_t)fd);
-    return VLC_SUCCESS;
-
-s_error:
-    gnutls_deinit (sys->session);
-    gnutls_certificate_free_credentials (sys->x509_cred);
-error:
-    gnutls_Deinit (VLC_OBJECT(session));
-    free (sys);
-    return VLC_EGENERIC;
-}
-
-
-static void CloseClient (vlc_tls_t *session)
-{
-    vlc_tls_sys_t *sys = session->sys;
-
-    if (sys->handshaked)
-        gnutls_bye (sys->session, GNUTLS_SHUT_WR);
-    gnutls_deinit (sys->session);
-    /* credentials must be free'd *after* gnutls_deinit() */
-    gnutls_certificate_free_credentials (sys->x509_cred);
-
-    gnutls_Deinit (VLC_OBJECT(session));
-    free (sys->hostname);
-    free (sys);
-}
-
-
-/**
- * Server-side TLS
- */
-struct vlc_tls_creds_sys
-{
-    gnutls_certificate_credentials_t x509_cred;
-    gnutls_dh_params_t               dh_params;
-    int                            (*handshake) (vlc_tls_t *);
-};
+    if (dialog_Question (tls, _("Insecure site"),
+        _("You attempted to reach %s. %s\n"
+          "This problem may be stem from an attempt to breach your security, "
+          "compromise your privacy, or a configuration error.\n\n"
+          "If in doubt, abort now.\n"),
+                         _("Abort"), _("View certificate"), NULL,
+                         vlc_gettext (msg), host) != 2)
+        goto failure;
 
+    gnutls_x509_crt_t cert;
 
-/**
- * Terminates TLS session and releases session data.
- * You still have to close the socket yourself.
- */
-static void gnutls_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session)
-{
-    vlc_tls_sys_t *sys = session->sys;
+    if (gnutls_x509_crt_init (&cert))
+        goto failure;
+    if (gnutls_x509_crt_import (cert, datum, GNUTLS_X509_FMT_DER)
+     || gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &desc))
+    {
+        gnutls_x509_crt_deinit (cert);
+        goto failure;
+    }
+    gnutls_x509_crt_deinit (cert);
 
-    if (sys->handshaked)
-        gnutls_bye (sys->session, GNUTLS_SHUT_WR);
-    gnutls_deinit (sys->session);
+    val = dialog_Question (tls, _("Insecure site"),
+         _("This is the certificate presented by %s:\n%s\n\n"
+           "If in doubt, abort now.\n"),
+                           _("Abort"), _("Accept 24 hours"),
+                           _("Accept permanently"), host, desc.data);
+    gnutls_free (desc.data);
 
-    free (sys);
-    (void) crd;
+    time_t expiry = 0;
+    switch (val)
+    {
+        case 2:
+            time (&expiry);
+            expiry += 24 * 60 * 60;
+        case 3:
+            val = gnutls_store_pubkey (NULL, NULL, host, service,
+                                       GNUTLS_CRT_X509, datum, expiry, 0);
+            if (val)
+                msg_Err (tls, "cannot store X.509 certificate: %s",
+                         gnutls_strerror (val));
+            goto success;
+    }
+    goto failure;
 }
 
-
 /**
- * Initializes a server-side TLS session.
+ * Initializes a client-side TLS credentials.
  */
-static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
-                               int fd)
+static int OpenClient (vlc_tls_creds_t *crd)
 {
-    vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
-    if (unlikely(sys == NULL))
-        return VLC_ENOMEM;
+    gnutls_certificate_credentials_t x509;
 
-    session->sys = sys;
-    session->sock.p_sys = session;
-    session->sock.pf_send = gnutls_Send;
-    session->sock.pf_recv = gnutls_Recv;
-    session->handshake = crd->sys->handshake;
-    sys->handshaked = false;
-    sys->hostname = NULL;
+    if (gnutls_Init (VLC_OBJECT(crd)))
+        return VLC_EGENERIC;
 
-    int val = gnutls_init (&sys->session, GNUTLS_SERVER);
+    int val = gnutls_certificate_allocate_credentials (&x509);
     if (val != 0)
     {
-        msg_Err (session, "cannot initialize TLS session: %s",
+        msg_Err (crd, "cannot allocate credentials: %s",
                  gnutls_strerror (val));
-        free (sys);
+        gnutls_Deinit ();
         return VLC_EGENERIC;
     }
 
-    if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
-        goto error;
-
-    val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
-                                  crd->sys->x509_cred);
+    val = gnutls_certificate_set_x509_system_trust (x509);
     if (val < 0)
-    {
-        msg_Err (session, "cannot set TLS session credentials: %s",
+        msg_Err (crd, "cannot load trusted Certificate Authorities: %s",
                  gnutls_strerror (val));
-        goto error;
-    }
+    else
+        msg_Dbg (crd, "loaded %d trusted CAs", val);
 
-    if (session->handshake == gnutls_HandshakeAndValidate)
-        gnutls_certificate_server_set_request (sys->session,
-                                               GNUTLS_CERT_REQUIRE);
+    gnutls_certificate_set_verify_flags (x509,
+                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
 
-    gnutls_transport_set_ptr (sys->session,
-                              (gnutls_transport_ptr_t)(intptr_t)fd);
-    return VLC_SUCCESS;
+    crd->sys = x509;
+    crd->open = gnutls_ClientSessionOpen;
+    crd->handshake = gnutls_ClientHandshake;
+    crd->close = gnutls_SessionClose;
 
-error:
-    gnutls_SessionClose (crd, session);
-    return VLC_EGENERIC;
+    return VLC_SUCCESS;
 }
 
-
-/**
- * Adds one or more Certificate Authorities to the trusted set.
- *
- * @param path (UTF-8) path to an X.509 certificates list.
- *
- * @return -1 on error, 0 on success.
- */
-static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
+static void CloseClient (vlc_tls_creds_t *crd)
 {
-    block_t *block = block_FilePath (path);
-    if (block == NULL)
-    {
-        msg_Err (crd, "cannot read trusted CA from %s: %m", path);
-        return VLC_EGENERIC;
-    }
-
-    gnutls_datum_t d = {
-       .data = block->p_buffer,
-       .size = block->i_buffer,
-    };
+    gnutls_certificate_credentials_t x509 = crd->sys;
 
-    int val = gnutls_certificate_set_x509_trust_mem (crd->sys->x509_cred, &d,
-                                                     GNUTLS_X509_FMT_PEM);
-    block_Release (block);
-    if (val < 0)
-    {
-        msg_Err (crd, "cannot load trusted CA from %s: %s", path,
-                 gnutls_strerror (val));
-        return VLC_EGENERIC;
-    }
-    msg_Dbg (crd, " %d trusted CA%s added from %s", val, (val != 1) ? "s" : "",
-             path);
-
-    /* enables peer's certificate verification */
-    crd->sys->handshake = gnutls_HandshakeAndValidate;
-    return VLC_SUCCESS;
+    gnutls_certificate_free_credentials (x509);
+    gnutls_Deinit ();
 }
 
-
+#ifdef ENABLE_SOUT
 /**
- * Adds a Certificates Revocation List to be sent to TLS clients.
- *
- * @param path (UTF-8) path of the CRL file.
- *
- * @return -1 on error, 0 on success.
+ * Server-side TLS credentials private data
  */
-static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
+typedef struct vlc_tls_creds_sys
 {
-    block_t *block = block_FilePath (path);
-    if (block == NULL)
-    {
-        msg_Err (crd, "cannot read CRL from %s: %m", path);
-        return VLC_EGENERIC;
-    }
+    gnutls_certificate_credentials_t x509_cred;
+    gnutls_dh_params_t dh_params;
+} vlc_tls_creds_sys_t;
 
-    gnutls_datum_t d = {
-       .data = block->p_buffer,
-       .size = block->i_buffer,
-    };
+/**
+ * Initializes a server-side TLS session.
+ */
+static int gnutls_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
+                                     int fd, const char *hostname,
+                                     const char *const *alpn)
+{
+    vlc_tls_creds_sys_t *sys = crd->sys;
 
-    int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
-                                                   GNUTLS_X509_FMT_PEM);
-    block_Release (block);
-    if (val < 0)
-    {
-        msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
-        return VLC_EGENERIC;
-    }
-    msg_Dbg (crd, "%d CRL%s added from %s", val, (val != 1) ? "s" : "", path);
-    return VLC_SUCCESS;
+    assert (hostname == NULL);
+    return gnutls_SessionOpen (tls, GNUTLS_SERVER, sys->x509_cred, fd, alpn);
 }
 
+static int gnutls_ServerHandshake (vlc_tls_t *tls, const char *host,
+                                   const char *service, char **restrict alp)
+{
+    int val = gnutls_ContinueHandshake (tls, alp);
+    if (val == 0)
+        tls->sock.p_sys = tls;
+
+    (void) host; (void) service;
+    return val;
+}
 
 /**
  * Allocates a whole server's TLS credentials.
@@ -785,15 +549,10 @@ static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
 
     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
     if (unlikely(sys == NULL))
-        goto error;
-
-    crd->sys     = sys;
-    crd->add_CA  = gnutls_AddCA;
-    crd->add_CRL = gnutls_AddCRL;
-    crd->open    = gnutls_SessionOpen;
-    crd->close   = gnutls_SessionClose;
-    /* No certificate validation by default */
-    sys->handshake  = gnutls_ContinueHandshake;
+    {
+        gnutls_Deinit ();
+        return VLC_ENOMEM;
+    }
 
     /* Sets server's credentials */
     val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
@@ -801,22 +560,26 @@ static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
     {
         msg_Err (crd, "cannot allocate credentials: %s",
                  gnutls_strerror (val));
-        goto error;
+        free (sys);
+        gnutls_Deinit ();
+        return VLC_ENOMEM;
     }
 
     block_t *certblock = block_FilePath (cert);
     if (certblock == NULL)
     {
-        msg_Err (crd, "cannot read certificate chain from %s: %m", cert);
-        return VLC_EGENERIC;
+        msg_Err (crd, "cannot read certificate chain from %s: %s", cert,
+                 vlc_strerror_c(errno));
+        goto error;
     }
 
     block_t *keyblock = block_FilePath (key);
     if (keyblock == NULL)
     {
-        msg_Err (crd, "cannot read private key from %s: %m", key);
+        msg_Err (crd, "cannot read private key from %s: %s", key,
+                 vlc_strerror_c(errno));
         block_Release (certblock);
-        return VLC_EGENERIC;
+        goto error;
     }
 
     gnutls_datum_t pub = {
@@ -861,11 +624,17 @@ static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
                  gnutls_strerror (val));
     }
 
+    crd->sys = sys;
+    crd->open = gnutls_ServerSessionOpen;
+    crd->handshake = gnutls_ServerHandshake;
+    crd->close = gnutls_SessionClose;
+
     return VLC_SUCCESS;
 
 error:
+    gnutls_certificate_free_credentials (sys->x509_cred);
     free (sys);
-    gnutls_Deinit (VLC_OBJECT(crd));
+    gnutls_Deinit ();
     return VLC_EGENERIC;
 }
 
@@ -880,6 +649,45 @@ static void CloseServer (vlc_tls_creds_t *crd)
     gnutls_certificate_free_credentials (sys->x509_cred);
     gnutls_dh_params_deinit (sys->dh_params);
     free (sys);
-
-    gnutls_Deinit (VLC_OBJECT(crd));
+    gnutls_Deinit ();
 }
+#endif
+
+#define PRIORITIES_TEXT N_("TLS cipher priorities")
+#define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
+    "hash functions and compression methods can be selected. " \
+    "Refer to GNU TLS documentation for detailed syntax.")
+static const char *const priorities_values[] = {
+    "PERFORMANCE",
+    "NORMAL",
+    "SECURE128",
+    "SECURE256",
+    "EXPORT",
+};
+static const char *const priorities_text[] = {
+    N_("Performance (prioritize faster ciphers)"),
+    N_("Normal"),
+    N_("Secure 128-bits (exclude 256-bits ciphers)"),
+    N_("Secure 256-bits (prioritize 256-bits ciphers)"),
+    N_("Export (include insecure ciphers)"),
+};
+
+vlc_module_begin ()
+    set_shortname( "GNU TLS" )
+    set_description( N_("GNU TLS transport layer security") )
+    set_capability( "tls client", 1 )
+    set_callbacks( OpenClient, CloseClient )
+    set_category( CAT_ADVANCED )
+    set_subcategory( SUBCAT_ADVANCED_NETWORK )
+    add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
+                PRIORITIES_LONGTEXT, false)
+        change_string_list (priorities_values, priorities_text)
+#ifdef ENABLE_SOUT
+    add_submodule ()
+        set_description( N_("GNU TLS server") )
+        set_capability( "tls server", 1 )
+        set_category( CAT_ADVANCED )
+        set_subcategory( SUBCAT_ADVANCED_NETWORK )
+        set_callbacks( OpenServer, CloseServer )
+#endif
+vlc_module_end ()