Commit db7117d2 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont
Browse files

gnutls: reorder, inline and turn off server-side if !SOUT

parent dd65f95c
/*****************************************************************************
* gnutls.c
*****************************************************************************
* Copyright (C) 2004-2012 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 Lesser General Public License as published by
......@@ -18,10 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
......@@ -40,53 +36,6 @@
#include <gnutls/x509.h>
#include "dhparams.h"
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int OpenClient (vlc_tls_creds_t *);
static void CloseClient (vlc_tls_creds_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_NETWORK )
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 )
add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
PRIORITIES_LONGTEXT, false)
change_string_list (priorities_values, priorities_text)
vlc_module_end ()
static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
/**
......@@ -202,6 +151,75 @@ static int gnutls_Recv (void *opaque, void *buf, size_t length)
return (val < 0) ? gnutls_Error (session, val) : val;
}
/**
* Terminates TLS session and releases session data.
* You still have to close the socket yourself.
*/
static void gnutls_SessionClose (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);
free (sys);
}
static int gnutls_SessionOpen (vlc_tls_t *tls, int type,
gnutls_certificate_credentials_t x509, int fd)
{
const char *errp;
int val;
vlc_tls_sys_t *sys = malloc (sizeof (*tls->sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
sys->handshaked = false;
val = gnutls_init (&sys->session, type);
if (val != 0)
{
msg_Err (tls, "cannot initialize TLS session: %s",
gnutls_strerror (val));
free (sys);
return VLC_EGENERIC;
}
char *priorities = var_InheritString (tls, "gnutls-priorities");
if (unlikely(priorities == NULL))
goto error;
val = gnutls_priority_set_direct (sys->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_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE, x509);
if (val < 0)
{
msg_Err (tls, "cannot set TLS session credentials: %s",
gnutls_strerror (val));
goto error;
}
gnutls_transport_set_ptr (sys->session,
(gnutls_transport_ptr_t)(intptr_t)fd);
tls->sys = sys;
tls->sock.p_sys = tls;
tls->sock.pf_send = gnutls_Send;
tls->sock.pf_recv = gnutls_Recv;
return VLC_SUCCESS;
error:
gnutls_SessionClose (tls);
return VLC_EGENERIC;
}
/**
* Starts or continues the TLS handshake.
......@@ -210,10 +228,9 @@ 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, const char *host,
const char *service)
static int gnutls_ContinueHandshake (vlc_tls_t *tls)
{
vlc_tls_sys_t *sys = session->sys;
vlc_tls_sys_t *sys = tls->sys;
int val;
#ifdef _WIN32
......@@ -222,81 +239,134 @@ static int gnutls_ContinueHandshake (vlc_tls_t *session, const char *host,
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);
msg_Dbg (tls, "TLS handshake: %s", gnutls_strerror (val));
switch (val)
{
case GNUTLS_E_SUCCESS:
return 0;
case GNUTLS_E_AGAIN:
case 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));
while (!gnutls_error_is_fatal (val));
if (val < 0)
{
#ifdef _WIN32
msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
msg_Dbg (tls, "Winsock error %d", WSAGetLastError ());
#endif
msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
msg_Err (tls, "TLS handshake error: %s", gnutls_strerror (val));
return -1;
}
static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
int fd, const char *hostname)
{
int val = gnutls_SessionOpen (tls, GNUTLS_CLIENT, crd->sys, fd);
if (val != VLC_SUCCESS)
return val;
vlc_tls_sys_t *sys = tls->sys;
/* minimum DH prime bits */
gnutls_dh_set_prime_bits (sys->session, 1024);
if (likely(hostname != NULL))
/* fill Server Name Indication */
gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
hostname, strlen (hostname));
return VLC_SUCCESS;
}
static int gnutls_ClientHandshake (vlc_tls_t *tls, const char *host,
const char *service)
{
int val = gnutls_ContinueHandshake (tls);
if (val)
return val;
/* certificates chain verification */
vlc_tls_sys_t *sys = tls->sys;
unsigned status;
val = gnutls_certificate_verify_peers3 (sys->session, host, &status);
if (val)
{
msg_Err (tls, "Certificate verification error: %s",
gnutls_strerror (val));
return -1;
}
sys->handshaked = true;
(void) host; (void) service;
return 0;
}
if (status == 0)
goto success; /* Good certificate */
/* Bad certificate */
gnutls_datum_t desc;
/**
* 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);
if (gnutls_certificate_verification_status_print(status,
gnutls_certificate_type_get (sys->session), &desc, 0) == 0)
{
msg_Err (tls, "Certificate verification failure: %s", desc.data);
gnutls_free (desc.data);
}
status &= ~GNUTLS_CERT_INVALID; /* always set / catch-all error */
status &= ~GNUTLS_CERT_SIGNER_NOT_FOUND; /* unknown CA */
status &= ~GNUTLS_CERT_UNEXPECTED_OWNER; /* mismatched hostname */
if (status != 0 || host == NULL)
return -1; /* Really bad certificate */
/* Look up mismatching certificate in store */
int val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
GNUTLS_CRT_X509, datum, 0);
const gnutls_datum_t *datum;
unsigned count;
datum = gnutls_certificate_get_peers (sys->session, &count);
if (datum == NULL || count == 0)
{
msg_Err (tls, "Peer certificate not available");
return -1;
}
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)
{
case 0:
msg_Dbg (obj, "certificate key match for %s", host);
return 0;
msg_Dbg (tls, "certificate key match for %s", host);
goto success;
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");
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 (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");
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 (obj, "certificate key match error for %s: %s", host,
msg_Err (tls, "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)
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)
return -1;
gnutls_x509_crt_t cert;
gnutls_datum_t desc;
if (gnutls_x509_crt_init (&cert))
return -1;
......@@ -308,7 +378,7 @@ static int gnutls_CertSearch (vlc_tls_t *obj, const char *host,
}
gnutls_x509_crt_deinit (cert);
val = dialog_Question (obj, _("Insecure site"),
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"),
......@@ -325,149 +395,65 @@ static int gnutls_CertSearch (vlc_tls_t *obj, const char *host,
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",
msg_Err (tls, "cannot store X.509 certificate: %s",
gnutls_strerror (val));
return 0;
}
return -1;
}
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, host, service);
if (val)
return val;
/* certificates chain verification */
unsigned status;
val = gnutls_certificate_verify_peers3 (sys->session, host, &status);
if (val)
{
msg_Err (session, "Certificate verification error: %s",
gnutls_strerror (val));
return -1;
}
if (status == 0)
return 0; /* good certificate */
gnutls_datum_t datum;
if (gnutls_certificate_verification_status_print(status,
gnutls_certificate_type_get (sys->session), &datum, 0) == 0)
{
msg_Err (session, "Certificate verification failure: %s", datum.data);
gnutls_free (datum.data);
}
status &= ~GNUTLS_CERT_INVALID; /* always set / catch-all error */
status &= ~GNUTLS_CERT_SIGNER_NOT_FOUND; /* unknown CA */
status &= ~GNUTLS_CERT_UNEXPECTED_OWNER; /* mismatched hostname */
if (status != 0 || host == NULL)
return -1; /* really bad certificate */
/* manual verification */
const gnutls_datum_t *data;
unsigned count;
data = gnutls_certificate_get_peers (sys->session, &count);
if (data == NULL || count == 0)
{
msg_Err (session, "Peer certificate not available");
return -1;
break;
default:
return -1;
}
msg_Dbg (session, "%u certificate(s) in the list", count);
return gnutls_CertSearch (session, host, service, data);
}
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;
success:
sys->handshaked = true;
return 0;
}
/**
* Terminates TLS session and releases session data.
* You still have to close the socket yourself.
* Initializes a client-side TLS credentials.
*/
static void gnutls_SessionClose (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);
free (sys);
}
static int gnutls_SessionOpen (vlc_tls_t *tls, int type,
gnutls_certificate_credentials_t x509, int fd)
static int OpenClient (vlc_tls_creds_t *crd)
{
vlc_tls_sys_t *sys = malloc (sizeof (*tls->sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
gnutls_certificate_credentials_t x509;
sys->handshaked = false;
if (gnutls_Init (VLC_OBJECT(crd)))
return VLC_EGENERIC;
int val = gnutls_init (&sys->session, type);
int val = gnutls_certificate_allocate_credentials (&x509);
if (val != 0)
{
msg_Err (tls, "cannot initialize TLS session: %s",
msg_Err (crd, "cannot allocate credentials: %s",
gnutls_strerror (val));
free (sys);
gnutls_Deinit (VLC_OBJECT(crd));
return VLC_EGENERIC;
}
if (gnutls_SessionPrioritize (VLC_OBJECT (tls), sys->session))
goto error;
val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE, x509);
val = gnutls_certificate_set_x509_system_trust (x509);
if (val < 0)
{
msg_Err (tls, "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);
gnutls_transport_set_ptr (sys->session,
(gnutls_transport_ptr_t)(intptr_t)fd);
gnutls_certificate_set_verify_flags (x509,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
crd->sys = x509;
crd->open = gnutls_ClientSessionOpen;
crd->handshake = gnutls_ClientHandshake;
crd->close = gnutls_SessionClose;
tls->sys = sys;
tls->sock.p_sys = tls;
tls->sock.pf_send = gnutls_Send;
tls->sock.pf_recv = gnutls_Recv;
return VLC_SUCCESS;
}
error:
gnutls_SessionClose (tls);
return VLC_EGENERIC;
static void CloseClient (vlc_tls_creds_t *crd)
{
gnutls_certificate_credentials_t x509 = crd->sys;
gnutls_certificate_free_credentials (x509);
gnutls_Deinit (VLC_OBJECT(crd));
}
#ifdef ENABLE_SOUT
/**
* Server-side TLS credentials private data
*/
......@@ -489,27 +475,17 @@ static int gnutls_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
return gnutls_SessionOpen (tls, GNUTLS_SERVER, sys->x509_cred, fd);
}
static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
int fd, const char *hostname)
static int gnutls_ServerHandshake (vlc_tls_t *tls, const char *host,
const char *service)
{
int val = gnutls_SessionOpen (tls, GNUTLS_CLIENT, crd->sys, fd);
if (val != VLC_SUCCESS)
return val;
int val = gnutls_ContinueHandshake (tls);
if (val == 0)
tls->sys->handshaked = true;
vlc_tls_sys_t *sys = tls->sys;
/* minimum DH prime bits */
gnutls_dh_set_prime_bits (sys->session, 1024);
if (likely(hostname != NULL))
/* fill Server Name Indication */
gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
hostname, strlen (hostname));
return VLC_SUCCESS;
(void) host; (void) service;
return val;
}
/**
* Allocates a whole server's TLS credentials.
*/
......@@ -594,7 +570,7 @@ static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
crd->sys = sys;
crd->open = gnutls_ServerSessionOpen;
crd->handshake = gnutls_ContinueHandshake;
crd->handshake = gnutls_ServerHandshake;
crd->close = gnutls_SessionClose;
return VLC_SUCCESS;
......@@ -619,49 +595,43 @@ static void CloseServer (vlc_tls_creds_t *crd)
gnutls_Deinit (VLC_OBJECT(crd));
}
#endif
/**
* Initializes a client-side TLS credentials.
*/
static int OpenClient (vlc_tls_creds_t *crd)
{
gnutls_certificate_credentials_t x509;
if (gnutls_Init (VLC_OBJECT(crd)))
return VLC_EGENERIC;
int val = gnutls_certificate_allocate_credentials (&x509);
if (val != 0)
{
msg_Err (crd, "cannot allocate credentials: %s",
gnutls_strerror (val));
gnutls_Deinit (VLC_OBJECT(crd));
return VLC_EGENERIC;
}
val = gnutls_certificate_set_x509_system_trust (x509);
if (val < 0)
msg_Err (crd, "cannot load trusted Certificate Authorities: %s",
gnutls_strerror (val));
else
msg_Dbg (crd, "loaded %d trusted CAs", val);
gnutls_certificate_set_verify_flags (x509,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
crd->sys = x509;
crd->open = gnutls_ClientSessionOpen;
crd->handshake = gnutls_HandshakeAndValidate;