Commit 42327f35 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

Rework/simplify the TLS plugin interface (LibVLC <-> tls plugin).

Remove the singleton pattern. Still very much work in progress.
parent 0753cb73
......@@ -411,7 +411,6 @@ typedef struct httpd_redirect_t httpd_redirect_t;
typedef struct httpd_stream_t httpd_stream_t;
/* TLS support */
typedef struct tls_t tls_t;
typedef struct tls_server_t tls_server_t;
typedef struct tls_session_t tls_session_t;
......
......@@ -60,7 +60,7 @@
#define VLC_OBJECT_FILTER (-22)
#define VLC_OBJECT_VOD (-23)
#define VLC_OBJECT_SPU (-24)
#define VLC_OBJECT_TLS (-25)
/*#define VLC_OBJECT_xxx (-25) - formerly TLS */
#define VLC_OBJECT_SD (-26)
#define VLC_OBJECT_XML (-27)
#define VLC_OBJECT_OSDMENU (-28)
......
/*****************************************************************************
* tls.c: TLS wrapper
* tls.c: Transport Layer Security API
*****************************************************************************
* Copyright (C) 2004-2005 the VideoLAN team
* Copyright (C) 2004-2007 the VideoLAN team
* $Id$
*
* Authors: Rémi Denis-Courmont <rem # videolan.org>
......@@ -30,26 +30,14 @@
# include <vlc_network.h>
struct tls_t
{
VLC_COMMON_MEMBERS
/* Module properties */
module_t *p_module;
void *p_sys;
tls_server_t * (*pf_server_create) ( tls_t *, const char *,
const char * );
tls_session_t * (*pf_client_create) ( tls_t * );
};
typedef struct tls_server_sys_t tls_server_sys_t;
struct tls_server_t
{
VLC_COMMON_MEMBERS
void *p_sys;
void (*pf_delete) ( tls_server_t * );
module_t *p_module;
tls_server_sys_t *p_sys;
int (*pf_add_CA) ( tls_server_t *, const char * );
int (*pf_add_CRL) ( tls_server_t *, const char * );
......@@ -57,11 +45,14 @@ struct tls_server_t
tls_session_t * (*pf_session_prepare) ( tls_server_t * );
};
typedef struct tls_session_sys_t tls_session_sys_t;
struct tls_session_t
{
VLC_COMMON_MEMBERS
void *p_sys;
module_t *p_module;
tls_session_sys_t *p_sys;
struct virtual_socket_t sock;
int (*pf_handshake) ( tls_session_t *, int, const char * );
......
......@@ -49,15 +49,17 @@
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#define DH_BITS 1024
#define CACHE_EXPIRATION 3600
#define DH_BITS 1024
#define CACHE_TIMEOUT 3600
#define CACHE_SIZE 64
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
static int OpenClient (vlc_object_t *);
static void CloseClient (vlc_object_t *);
static int OpenServer (vlc_object_t *);
static void CloseServer (vlc_object_t *);
#define DH_BITS_TEXT N_("Diffie-Hellman prime bits")
#define DH_BITS_LONGTEXT N_( \
......@@ -65,8 +67,8 @@ static void Close( vlc_object_t * );
"used for TLS or SSL-based server-side encryption. This is generally " \
"not needed." )
#define CACHE_EXPIRATION_TEXT N_("Expiration time for resumed TLS sessions")
#define CACHE_EXPIRATION_LONGTEXT N_( \
#define CACHE_TIMEOUT_TEXT N_("Expiration time for resumed TLS sessions")
#define CACHE_TIMEOUT_LONGTEXT N_( \
"It is possible to cache the resumed TLS sessions. This is the expiration "\
"time of the sessions stored in this cache, in seconds." )
......@@ -82,9 +84,9 @@ static void Close( vlc_object_t * );
vlc_module_begin();
set_shortname( "GnuTLS" );
set_description( _("GnuTLS TLS encryption layer") );
set_capability( "tls", 1 );
set_callbacks( Open, Close );
set_description( _("GnuTLS transport layer security") );
set_capability( "tls client", 1 );
set_callbacks( OpenClient, CloseClient );
set_category( CAT_ADVANCED );
set_subcategory( SUBCAT_ADVANCED_MISC );
......@@ -92,55 +94,135 @@ vlc_module_begin();
CHECK_CERT_LONGTEXT, VLC_FALSE );
add_obsolete_bool( "tls-check-hostname" );
add_integer( "gnutls-dh-bits", DH_BITS, NULL, DH_BITS_TEXT,
DH_BITS_LONGTEXT, VLC_TRUE );
add_integer( "gnutls-cache-expiration", CACHE_EXPIRATION, NULL,
CACHE_EXPIRATION_TEXT, CACHE_EXPIRATION_LONGTEXT, VLC_TRUE );
add_integer( "gnutls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
CACHE_SIZE_LONGTEXT, VLC_TRUE );
add_submodule();
set_description( _("GnuTLS server") );
set_capability( "tls server", 1 );
set_category( CAT_ADVANCED );
set_subcategory( SUBCAT_ADVANCED_MISC );
set_callbacks( OpenServer, CloseServer );
add_integer( "gnutls-dh-bits", DH_BITS, NULL, DH_BITS_TEXT,
DH_BITS_LONGTEXT, VLC_TRUE );
add_integer( "gnutls-cache-timeout", CACHE_TIMEOUT, NULL,
CACHE_TIMEOUT_TEXT, CACHE_TIMEOUT_LONGTEXT, VLC_TRUE );
add_integer( "gnutls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
CACHE_SIZE_LONGTEXT, VLC_TRUE );
vlc_module_end();
#define MAX_SESSION_ID 32
#define MAX_SESSION_DATA 1024
typedef struct saved_session_t
#ifdef LIBVLC_USE_PTHREAD
GCRY_THREAD_OPTION_PTHREAD_IMPL;
# define gcry_threads_vlc gcry_threads_pthread
#else
/**
* gcrypt thread option VLC implementation
*/
# define NEED_THREAD_CONTEXT 1
static vlc_object_t *__p_gcry_data = NULL;
static int gcry_vlc_mutex_init( void **p_sys )
{
char id[MAX_SESSION_ID];
char data[MAX_SESSION_DATA];
int i_val;
vlc_mutex_t *p_lock = (vlc_mutex_t *)malloc( sizeof( vlc_mutex_t ) );
unsigned i_idlen;
unsigned i_datalen;
} saved_session_t;
if( p_lock == NULL)
return ENOMEM;
i_val = vlc_mutex_init( __p_gcry_data, p_lock );
if( i_val )
free( p_lock );
else
*p_sys = p_lock;
return i_val;
}
typedef struct tls_server_sys_t
static int gcry_vlc_mutex_destroy( void **p_sys )
{
gnutls_certificate_credentials x509_cred;
gnutls_dh_params dh_params;
int i_val;
vlc_mutex_t *p_lock = (vlc_mutex_t *)*p_sys;
struct saved_session_t *p_cache;
struct saved_session_t *p_store;
int i_cache_size;
vlc_mutex_t cache_lock;
i_val = vlc_mutex_destroy( p_lock );
free( p_lock );
return i_val;
}
int (*pf_handshake2)( tls_session_t * );
} tls_server_sys_t;
static int gcry_vlc_mutex_lock( void **p_sys )
{
return vlc_mutex_lock( (vlc_mutex_t *)*p_sys );
}
static int gcry_vlc_mutex_unlock( void **lock )
{
return vlc_mutex_unlock( (vlc_mutex_t *)*lock );
}
typedef struct tls_session_sys_t
static struct gcry_thread_cbs gcry_threads_vlc =
{
gnutls_session session;
char *psz_hostname;
vlc_bool_t b_handshaked;
} tls_session_sys_t;
GCRY_THREAD_OPTION_USER,
NULL,
gcry_vlc_mutex_init,
gcry_vlc_mutex_destroy,
gcry_vlc_mutex_lock,
gcry_vlc_mutex_unlock
};
#endif
typedef struct tls_client_sys_t
/**
* Initializes GnuTLS with proper locking.
* @return VLC_SUCCESS on success, a VLC error code otherwise.
*/
static int gnutls_Init (vlc_object_t *p_this)
{
struct tls_session_sys_t session;
gnutls_certificate_credentials x509_cred;
} tls_client_sys_t;
int ret = VLC_EGENERIC;
vlc_mutex_t *lock = var_GetGlobalMutex ("gnutls_mutex");
vlc_mutex_lock (lock);
/* This should probably be removed/fixed. It will screw up with multiple
* LibVLC instances. */
#ifdef NEED_THREAD_CONTEXT
__p_gcry_data = VLC_OBJECT (p_this->p_libvlc);
#endif
gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_vlc);
if (gnutls_global_init ())
{
msg_Err (p_this, "cannot initialize GnuTLS");
goto error;
}
const char *psz_version = gnutls_check_version ("1.3.3");
if (psz_version == NULL)
{
msg_Err (p_this, "unsupported GnuTLS version");
gnutls_global_deinit ();
goto error;
}
msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
ret = VLC_SUCCESS;
error:
vlc_mutex_unlock (lock);
return ret;
}
/**
* Deinitializes GnuTLS.
*/
static void gnutls_Deinit (vlc_object_t *p_this)
{
vlc_mutex_t *lock = var_GetGlobalMutex( "gnutls_mutex" );
vlc_mutex_lock (lock);
gnutls_global_deinit ();
msg_Dbg (p_this, "GnuTLS deinitialized");
vlc_mutex_unlock (lock);
}
static int gnutls_Error (vlc_object_t *obj, int val)
......@@ -148,15 +230,15 @@ static int gnutls_Error (vlc_object_t *obj, int val)
switch (val)
{
case GNUTLS_E_AGAIN:
#if ! defined(WIN32)
#ifndef WIN32
errno = EAGAIN;
break;
#endif
/* WinSock does not return EAGAIN, return EINTR instead */
case GNUTLS_E_INTERRUPTED:
#if defined(WIN32)
WSASetLastError(WSAEINTR);
#ifdef WIN32
WSASetLastError (WSAEINTR);
#else
errno = EINTR;
#endif
......@@ -164,12 +246,12 @@ static int gnutls_Error (vlc_object_t *obj, int val)
default:
msg_Err (obj, "%s", gnutls_strerror (val));
#ifdef DEBUG
#ifndef NDEBUG
if (!gnutls_error_is_fatal (val))
msg_Err (obj, "Error above should be handled");
#endif
#if defined(WIN32)
WSASetLastError(WSAECONNRESET);
#ifdef WIN32
WSASetLastError (WSAECONNRESET);
#else
errno = ECONNRESET;
#endif
......@@ -178,6 +260,14 @@ static int gnutls_Error (vlc_object_t *obj, int val)
}
struct tls_session_sys_t
{
gnutls_session session;
char *psz_hostname;
vlc_bool_t b_handshaked;
};
/**
* Sends data through a TLS session.
*/
......@@ -210,14 +300,11 @@ gnutls_Recv( void *p_session, void *buf, int i_length )
}
/*****************************************************************************
* tls_Session(Continue)?Handshake:
*****************************************************************************
* Establishes TLS session with a peer through socket <fd>.
* Returns -1 on error (you need not and must not call tls_SessionClose)
/**
* @return -1 on error (you need not and must not call tls_SessionClose())
* 0 on succesful handshake completion, 1 if more would-be blocking recv is
* needed, 2 if more would-be blocking send is required.
*****************************************************************************/
*/
static int
gnutls_ContinueHandshake( tls_session_t *p_session)
{
......@@ -385,9 +472,7 @@ static int
gnutls_BeginHandshake( tls_session_t *p_session, int fd,
const char *psz_hostname )
{
tls_session_sys_t *p_sys;
p_sys = (tls_session_sys_t *)(p_session->p_sys);
tls_session_sys_t *p_sys = p_session->p_sys;
gnutls_transport_set_ptr (p_sys->session, (gnutls_transport_ptr)(intptr_t)fd);
......@@ -406,31 +491,6 @@ gnutls_BeginHandshake( tls_session_t *p_session, int fd,
return p_session->pf_handshake2( p_session );
}
/**
* Terminates TLS session and releases session data.
* You still have to close the socket yourself.
*/
static void
gnutls_SessionClose( tls_session_t *p_session )
{
tls_session_sys_t *p_sys;
p_sys = (tls_session_sys_t *)(p_session->p_sys);
if( p_sys->b_handshaked == VLC_TRUE )
gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
gnutls_deinit( p_sys->session );
if( p_sys->psz_hostname != NULL )
free( p_sys->psz_hostname );
vlc_object_detach( p_session );
vlc_object_destroy( p_session );
free( p_sys );
}
typedef int (*tls_prio_func) (gnutls_session_t, const int *);
static int
......@@ -535,20 +595,6 @@ gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
}
static void
gnutls_ClientDelete( tls_session_t *p_session )
{
/* On the client-side, credentials are re-allocated per session */
gnutls_certificate_credentials x509_cred =
((tls_client_sys_t *)(p_session->p_sys))->x509_cred;
gnutls_SessionClose( p_session );
/* credentials must be free'd *after* gnutls_deinit() */
gnutls_certificate_free_credentials( x509_cred );
}
static int
gnutls_Addx509File( vlc_object_t *p_this,
gnutls_certificate_credentials cred,
......@@ -659,62 +705,65 @@ gnutls_Addx509File( vlc_object_t *p_this,
}
/** TLS client session data */
typedef struct tls_client_sys_t
{
struct tls_session_sys_t session;
gnutls_certificate_credentials x509_cred;
} tls_client_sys_t;
/**
* Initializes a client-side TLS session.
*/
static tls_session_t *
gnutls_ClientCreate( tls_t *p_tls )
static int OpenClient (vlc_object_t *obj)
{
tls_session_t *p_session = NULL;
tls_client_sys_t *p_sys = NULL;
tls_session_t *p_session = (tls_session_t *)obj;
int i_val;
p_sys = (tls_client_sys_t *)malloc( sizeof(struct tls_client_sys_t) );
if( p_sys == NULL )
return NULL;
if (gnutls_Init (obj))
return VLC_EGENERIC;
p_session = (struct tls_session_t *)vlc_object_create ( p_tls, sizeof(struct tls_session_t) );
if( p_session == NULL )
tls_client_sys_t *p_sys = malloc (sizeof (*p_sys));
if (p_sys == NULL)
{
free( p_sys );
return NULL;
gnutls_Deinit (obj);
return VLC_ENOMEM;
}
p_session->p_sys = p_sys;
p_session->p_sys = &p_sys->session;
p_session->sock.p_sys = p_session;
p_session->sock.pf_send = gnutls_Send;
p_session->sock.pf_recv = gnutls_Recv;
p_session->pf_handshake = gnutls_BeginHandshake;
p_session->pf_close = gnutls_ClientDelete;
p_session->pf_close = NULL;
p_sys->session.b_handshaked = VLC_FALSE;
p_sys->session.psz_hostname = NULL;
vlc_object_attach( p_session, p_tls );
const char *homedir = p_tls->p_libvlc->psz_datadir,
const char *homedir = obj->p_libvlc->psz_datadir,
*datadir = config_GetDataDir ();
size_t l1 = strlen (homedir), l2 = strlen (datadir);
char path[((l1 > l2) ? l1 : l2) + sizeof ("/ssl/private")];
// > sizeof ("/ssl/certs")
// > sizeof ("/ca-certificates.crt")
i_val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
if( i_val != 0 )
i_val = gnutls_certificate_allocate_credentials (&p_sys->x509_cred);
if (i_val != 0)
{
msg_Err( p_tls, "cannot allocate X509 credentials: %s",
gnutls_strerror( i_val ) );
msg_Err (obj, "cannot allocate X509 credentials: %s",
gnutls_strerror (i_val));
goto error;
}
if (var_CreateGetBool (p_tls, "tls-check-cert"))
if (var_CreateGetBool (obj, "tls-check-cert"))
{
sprintf (path, "%s/ssl/certs", homedir);
gnutls_Addx509Directory ((vlc_object_t *)p_session,
gnutls_Addx509Directory (VLC_OBJECT (p_session),
p_sys->x509_cred, path, VLC_FALSE);
sprintf (path, "%s/ca-certificates.crt", datadir);
gnutls_Addx509File ((vlc_object_t *)p_session,
gnutls_Addx509File (VLC_OBJECT (p_session),
p_sys->x509_cred, path, VLC_FALSE);
p_session->pf_handshake2 = gnutls_HandshakeAndValidate;
}
......@@ -722,15 +771,15 @@ gnutls_ClientCreate( tls_t *p_tls )
p_session->pf_handshake2 = gnutls_ContinueHandshake;
sprintf (path, "%s/ssl/private", homedir);
gnutls_Addx509Directory ((vlc_object_t *)p_session, p_sys->x509_cred,
gnutls_Addx509Directory (VLC_OBJECT (p_session), p_sys->x509_cred,
path, VLC_TRUE);
i_val = gnutls_init( &p_sys->session.session, GNUTLS_CLIENT );
if( i_val != 0 )
if (i_val != 0)
{
msg_Err( p_tls, "cannot initialize TLS session: %s",
gnutls_strerror( i_val ) );
gnutls_certificate_free_credentials( p_sys->x509_cred );
msg_Err (obj, "cannot initialize TLS session: %s",
gnutls_strerror (i_val));
gnutls_certificate_free_credentials (p_sys->x509_cred);
goto error;
}
......@@ -738,34 +787,78 @@ gnutls_ClientCreate( tls_t *p_tls )
p_sys->session.session))
goto s_error;
i_val = gnutls_credentials_set( p_sys->session.session,
i_val = gnutls_credentials_set (p_sys->session.session,
GNUTLS_CRD_CERTIFICATE,
p_sys->x509_cred );
if( i_val < 0 )
p_sys->x509_cred);
if (i_val < 0)
{
msg_Err( p_tls, "cannot set TLS session credentials: %s",
gnutls_strerror( i_val ) );
msg_Err (obj, "cannot set TLS session credentials: %s",
gnutls_strerror (i_val));
goto s_error;
}
return p_session;
return VLC_SUCCESS;
s_error:
gnutls_deinit( p_sys->session.session );
gnutls_certificate_free_credentials( p_sys->x509_cred );
gnutls_deinit (p_sys->session.session);
gnutls_certificate_free_credentials (p_sys->x509_cred);
error:
vlc_object_detach( p_session );
vlc_object_destroy( p_session );
free( p_sys );
gnutls_Deinit (obj);
free (p_sys);
return VLC_EGENERIC;
}
return NULL;
static void CloseClient (vlc_object_t *obj)
{
tls_session_t *client = (tls_session_t *)obj;
tls_client_sys_t *p_sys = (tls_client_sys_t *)(client->p_sys);
if (p_sys->session.b_handshaked == VLC_TRUE)
gnutls_bye (p_sys->session.session, GNUTLS_SHUT_WR);
gnutls_deinit (p_sys->session.session);
/* credentials must be free'd *after* gnutls_deinit() */
gnutls_certificate_free_credentials (p_sys->x509_cred);
gnutls_Deinit (obj);
free (p_sys->session.psz_hostname);
free (p_sys);
}
/**
* Server-side TLS
*/
struct tls_server_sys_t
{
gnutls_certificate_credentials x509_cred;
gnutls_dh_params dh_params;
struct saved_session_t *p_cache;
struct saved_session_t *p_store;
int i_cache_size;
vlc_mutex_t cache_lock;
int (*pf_handshake2)( tls_session_t * );
};
/**
* TLS session resumption callbacks (server-side)
*/
#define MAX_SESSION_ID 32
#define MAX_SESSION_DATA 1024
typedef struct saved_session_t
{
char id[MAX_SESSION_ID];
char data[MAX_SESSION_DATA];
unsigned i_idlen;
unsigned i_datalen;
} saved_session_t;
static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
{
tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
......@@ -792,10 +885,9 @@ static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
}
static const gnutls_datum err_datum = { NULL, 0 };
static gnutls_datum cb_fetch( void *p_server, gnutls_datum key )
{
static const gnutls_datum err_datum = { NULL, 0 };
tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
saved_session_t *p_session, *p_end;
......@@ -861,6 +953,26 @@ static int cb_delete( void *p_server, gnutls_datum key )
}
/**
* Terminates TLS session and releases session data.
* You still have to close the socket yourself.
*/
static void
gnutls_SessionClose( tls_session_t *p_session )
{
tls_session_sys_t *p_sys = p_session->p_sys;
if( p_sys->b_handshaked == VLC_TRUE )
gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
gnutls_deinit( p_sys->session );
vlc_object_detach( p_session );
vlc_object_destroy( p_session );
free( p_sys );
}
/**
* Initializes a server-side TLS session.
*/
......@@ -885,7 +997,7 @@ gnutls_ServerSessionPrepare( tls_server_t *p_server )
vlc_object_attach( p_session, p_server );