]> git.sesse.net Git - vlc/blobdiff - modules/misc/gnutls.c
modules: factor Makefile statement
[vlc] / modules / misc / gnutls.c
index 35873070a17d9d27c8d70f0a21ce68eee0689d2e..4a6056abe1d237c350c9b3202bdd97fb61ac5d45 100644 (file)
@@ -26,6 +26,7 @@
 # include "config.h"
 #endif
 
+#include <time.h>
 #include <errno.h>
 #include <assert.h>
 
 #include <vlc_plugin.h>
 #include <vlc_tls.h>
 #include <vlc_block.h>
+#include <vlc_dialog.h>
 
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
-#if (GNUTLS_VERSION_NUMBER < 0x030014)
-# define gnutls_certificate_set_x509_system_trust(c) \
-    (c, GNUTLS_E_UNIMPLEMENTED_FEATURE)
-#endif
-
 #include "dhparams.h"
 
 /*****************************************************************************
@@ -76,13 +73,13 @@ vlc_module_begin ()
     set_capability( "tls client", 1 )
     set_callbacks( OpenClient, CloseClient )
     set_category( CAT_ADVANCED )
-    set_subcategory( SUBCAT_ADVANCED_MISC )
+    set_subcategory( SUBCAT_ADVANCED_NETWORK )
 
     add_submodule ()
         set_description( N_("GNU TLS server") )
         set_capability( "tls server", 1 )
         set_category( CAT_ADVANCED )
-        set_subcategory( SUBCAT_ADVANCED_MISC )
+        set_subcategory( SUBCAT_ADVANCED_NETWORK )
         set_callbacks( OpenServer, CloseServer )
 
         add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
@@ -107,7 +104,7 @@ static int gnutls_Init (vlc_object_t *p_this)
         goto error;
     }
 
-    const char *psz_version = gnutls_check_version ("2.6.6");
+    const char *psz_version = gnutls_check_version ("3.0.20");
     if (psz_version == NULL)
     {
         msg_Err (p_this, "unsupported GnuTLS version");
@@ -142,7 +139,7 @@ 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;
@@ -150,7 +147,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;
@@ -163,7 +160,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;
@@ -176,7 +173,6 @@ static int gnutls_Error (vlc_object_t *obj, int val)
 struct vlc_tls_sys
 {
     gnutls_session_t session;
-    char *hostname; /* XXX: client only */
     bool handshaked;
 };
 
@@ -214,21 +210,29 @@ static int gnutls_Recv (void *opaque, void *buf, size_t length)
  * 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_ContinueHandshake (vlc_tls_t *session, const char *host,
+                                     const char *service)
 {
     vlc_tls_sys_t *sys = session->sys;
     int val;
 
-#ifdef WIN32
+#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);
+    do
+    {
+        val = gnutls_handshake (sys->session);
+        msg_Dbg (session, "TLS handshake: %s", gnutls_strerror (val));
+
+        if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
+            /* I/O event: return to caller's poll() loop */
+            return 1 + gnutls_record_get_direction (sys->session);
+    }
+    while (val < 0 && !gnutls_error_is_fatal (val));
 
     if (val < 0)
     {
-#ifdef WIN32
+#ifdef _WIN32
         msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
 #endif
         msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
@@ -236,38 +240,121 @@ static int gnutls_ContinueHandshake (vlc_tls_t *session)
     }
 
     sys->handshaked = true;
+    (void) host; (void) service;
     return 0;
 }
 
 
+/**
+ * Looks up certificate in known hosts data base.
+ * @return 0 on success, -1 on failure.
+ */
+static int gnutls_CertSearch (vlc_tls_t *obj, const char *host,
+                              const char *service,
+                              const gnutls_datum_t *restrict datum)
+{
+    assert (host != NULL);
+
+    /* Look up mismatching certificate in store */
+    int val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
+                                           GNUTLS_CRT_X509, datum, 0);
+    const char *msg;
+    switch (val)
+    {
+        case 0:
+            msg_Dbg (obj, "certificate key match for %s", host);
+            return 0;
+        case GNUTLS_E_NO_CERTIFICATE_FOUND:
+            msg_Dbg (obj, "no known certificates for %s", host);
+            msg = N_("You attempted to reach %s. "
+                "However the security certificate presented by the server "
+                "is unknown and could not be authenticated by any trusted "
+                "Certification Authority. "
+                "This problem may be caused by a configuration error "
+                "or an attempt to breach your security or your privacy.\n\n"
+                "If in doubt, abort now.\n");
+            break;
+        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
+            msg_Dbg (obj, "certificate keys mismatch for %s", host);
+            msg = N_("You attempted to reach %s. "
+                "However the security certificate presented by the server "
+                "changed since the previous visit "
+                "and was not authenticated by any trusted "
+                "Certification Authority. "
+                "This problem may be caused by a configuration error "
+                "or an attempt to breach your security or your privacy.\n\n"
+                "If in doubt, abort now.\n");
+            break;
+        default:
+            msg_Err (obj, "certificate key match error for %s: %s", host,
+                     gnutls_strerror (val));
+            return -1;
+    }
+
+    if (dialog_Question (obj, _("Insecure site"), vlc_gettext (msg),
+                         _("Abort"), _("View certificate"), NULL, host) != 2)
+        return -1;
+
+    gnutls_x509_crt_t cert;
+    gnutls_datum_t desc;
+
+    if (gnutls_x509_crt_init (&cert))
+        return -1;
+    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);
+        return -1;
+    }
+    gnutls_x509_crt_deinit (cert);
+
+    val = dialog_Question (obj, _("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);
+
+    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 (obj, "cannot store X.509 certificate: %s",
+                         gnutls_strerror (val));
+            return 0;
+    }
+    return -1;
+}
+
+
 static struct
 {
-    int flag;
-    const char msg[44];
+    unsigned flag;
+    const char msg[29];
 } cert_errs[] =
 {
-    { 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" },
+    { GNUTLS_CERT_INVALID,            "Certificate not verified"     },
+    { GNUTLS_CERT_REVOKED,            "Certificate revoked"          },
+    { GNUTLS_CERT_SIGNER_NOT_FOUND,   "Signer not found"             },
+    { GNUTLS_CERT_SIGNER_NOT_CA,      "Signer not a CA"              },
+    { GNUTLS_CERT_INSECURE_ALGORITHM, "Signature algorithm insecure" },
+    { GNUTLS_CERT_NOT_ACTIVATED,      "Certificate not activated"    },
+    { GNUTLS_CERT_EXPIRED,            "Certificate expired"          },
 };
 
 
-static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
+static int gnutls_HandshakeAndValidate (vlc_tls_t *session, const char *host,
+                                        const char *service)
 {
     vlc_tls_sys_t *sys = session->sys;
 
-    int val = gnutls_ContinueHandshake (session);
+    int val = gnutls_ContinueHandshake (session, host, service);
     if (val)
         return val;
 
@@ -281,30 +368,31 @@ static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
                  gnutls_strerror (val));
         return -1;
     }
-
     if (status)
     {
-        msg_Err (session, "Certificate verification failure:");
+        msg_Err (session, "Certificate verification failure (0x%04X)", status);
         for (size_t i = 0; i < sizeof (cert_errs) / sizeof (cert_errs[0]); i++)
             if (status & cert_errs[i].flag)
-            {
                 msg_Err (session, " * %s", cert_errs[i].msg);
-                status &= ~cert_errs[i].flag;
-            }
-
-        if (status)
-            msg_Err (session, " * Unknown verification error 0x%04X", status);
-        return -1;
+        if (status & ~(GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND))
+            return -1;
     }
 
     /* certificate (host)name verification */
     const gnutls_datum_t *data;
-    data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
-    if (data == NULL)
+    unsigned count;
+    data = gnutls_certificate_get_peers (sys->session, &count);
+    if (data == NULL || count == 0)
     {
         msg_Err (session, "Peer certificate not available");
         return -1;
     }
+    msg_Dbg (session, "%u certificate(s) in the list", count);
+
+    if (val || host == NULL)
+        return val;
+    if (status && gnutls_CertSearch (session, host, service, data))
+        return -1;
 
     gnutls_x509_crt_t cert;
     val = gnutls_x509_crt_init (&cert);
@@ -322,20 +410,15 @@ static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
         goto error;
     }
 
-    if (sys->hostname != NULL
-     && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
+    val = !gnutls_x509_crt_check_hostname (cert, host);
+    if (val)
     {
-        msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
-        goto error;
+        msg_Err (session, "Certificate does not match \"%s\"", host);
+        val = gnutls_CertSearch (session, host, service, data);
     }
-
-    gnutls_x509_crt_deinit (cert);
-    msg_Dbg (session, "TLS/X.509 certificate verified");
-    return 0;
-
 error:
     gnutls_x509_crt_deinit (cert);
-    return -1;
+    return val;
 }
 
 static int
@@ -367,7 +450,8 @@ struct vlc_tls_creds_sys
 {
     gnutls_certificate_credentials_t x509_cred;
     gnutls_dh_params_t dh_params; /* XXX: used for server only */
-    int (*handshake) (vlc_tls_t *); /* XXX: useful for server only */
+    int (*handshake) (vlc_tls_t *, const char *, const char *);
+        /* ^^ XXX: useful for server only */
 };
 
 
@@ -383,7 +467,6 @@ static void gnutls_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session)
         gnutls_bye (sys->session, GNUTLS_SHUT_WR);
     gnutls_deinit (sys->session);
 
-    free (sys->hostname);
     free (sys);
     (void) crd;
 }
@@ -405,7 +488,6 @@ static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
     session->sock.pf_recv = gnutls_Recv;
     session->handshake = crd->sys->handshake;
     sys->handshaked = false;
-    sys->hostname = NULL;
 
     int val = gnutls_init (&sys->session, type);
     if (val != 0)
@@ -463,22 +545,12 @@ static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
     /* minimum DH prime bits */
     gnutls_dh_set_prime_bits (sys->session, 1024);
 
-    /* server name */
     if (likely(hostname != NULL))
-    {
         /* 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 error;
-    }
 
     return VLC_SUCCESS;
-error:
-    gnutls_SessionClose (crd, session);
-    return VLC_EGENERIC;
 }