# 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"
/*****************************************************************************
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,
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");
switch (val)
{
case GNUTLS_E_AGAIN:
-#ifdef WIN32
+#ifdef _WIN32
WSASetLastError (WSAEWOULDBLOCK);
#else
errno = EAGAIN;
break;
case GNUTLS_E_INTERRUPTED:
-#ifdef WIN32
+#ifdef _WIN32
WSASetLastError (WSAEINTR);
#else
errno = EINTR;
if (!gnutls_error_is_fatal (val))
msg_Err (obj, "Error above should be handled");
#endif
-#ifdef WIN32
+#ifdef _WIN32
WSASetLastError (WSAECONNRESET);
#else
errno = ECONNRESET;
struct vlc_tls_sys
{
gnutls_session_t session;
- char *hostname; /* XXX: client only */
bool handshaked;
};
* 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));
}
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;
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);
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
{
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 */
};
gnutls_bye (sys->session, GNUTLS_SHUT_WR);
gnutls_deinit (sys->session);
- free (sys->hostname);
free (sys);
(void) crd;
}
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)
/* 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;
}