X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fmisc%2Fgnutls.c;h=5f6061342010f82cd65a92291d97dade1e4839ce;hb=9389e7ccc5970976e81f069b481cee13f7aacc20;hp=f75766947732a687630e51a61a9a6c9de308884b;hpb=73ae5d90d0ec1d6402625a5136720dc168e761f3;p=vlc diff --git a/modules/misc/gnutls.c b/modules/misc/gnutls.c index f757669477..5f60613420 100644 --- a/modules/misc/gnutls.c +++ b/modules/misc/gnutls.c @@ -2,9 +2,9 @@ * tls.c ***************************************************************************** * Copyright (C) 2004-2005 VideoLAN - * $Id: httpd.c 8263 2004-07-24 09:06:58Z courmisch $ + * $Id$ * - * Authors: Remi Denis-Courmont + * Authors: Remi 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 @@ -23,9 +23,7 @@ /* * TODO: - * - fix FIXMEs, - * - server-side client cert validation, - * - client-side server cert validation (?). + * - fix FIXMEs */ @@ -36,6 +34,19 @@ #include #include +#include +#include +#ifdef HAVE_DIRENT_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +# ifdef HAVE_UNISTD_H +# include +# endif +#endif + + #include "vlc_tls.h" #include @@ -66,14 +77,30 @@ static void Close( vlc_object_t * ); "Allows you to modify the maximum number of resumed TLS sessions that " \ "the cache will hold." ) +#define CHECK_CERT_TEXT N_("Check TLS/SSL server certificate validity") +#define CHECK_CERT_LONGTEXT N_( \ + "Ensures that server certificate is valid " \ + "(ie. signed by an approved Certificate Authority)." ) + +#define CHECK_HOSTNAME_TEXT N_("Check TLS/SSL server hostname in certificate") +#define CHECK_HOSTNAME_LONGTEXT N_( \ + "Ensures that server hostname in certificate match requested host name." ) vlc_module_begin(); + set_shortname( "GnuTLS" ); set_description( _("GnuTLS TLS encryption layer") ); set_capability( "tls", 1 ); set_callbacks( Open, Close ); set_category( CAT_ADVANCED ); set_subcategory( SUBCAT_ADVANCED_MISC ); + add_bool( "tls-check-cert", VLC_FALSE, NULL, CHECK_CERT_TEXT, + CHECK_CERT_LONGTEXT, VLC_FALSE ); +#if 0 + add_bool( "tls-check-hostname", VLC_FALSE, NULL, CHECK_HOSTNAME_TEXT, + CHECK_HOSTNAME_LONGTEXT, VLC_FALSE ); +#endif + add_integer( "dh-bits", DH_BITS, NULL, DH_BITS_TEXT, DH_BITS_LONGTEXT, VLC_TRUE ); add_integer( "tls-cache-expiration", CACHE_EXPIRATION, NULL, @@ -105,6 +132,8 @@ typedef struct tls_server_sys_t struct saved_session_t *p_store; int i_cache_size; vlc_mutex_t cache_lock; + + int (*pf_handshake2)( tls_session_t * ); } tls_server_sys_t; @@ -123,7 +152,7 @@ typedef struct tls_client_sys_t static int -_get_Int (vlc_object_t *p_this, const char *var) +_get_Int( vlc_object_t *p_this, const char *var ) { vlc_value_t value; @@ -136,7 +165,22 @@ _get_Int (vlc_object_t *p_this, const char *var) return value.i_int; } +static int +_get_Bool( vlc_object_t *p_this, const char *var ) +{ + vlc_value_t value; + + if( var_Get( p_this, var, &value ) != VLC_SUCCESS ) + { + var_Create( p_this, var, VLC_VAR_BOOL | VLC_VAR_DOINHERIT ); + var_Get( p_this, var, &value ); + } + + return value.b_bool; +} + #define get_Int( a, b ) _get_Int( (vlc_object_t *)(a), (b) ) +#define get_Bool( a, b ) _get_Bool( (vlc_object_t *)(a), (b) ) /***************************************************************************** @@ -186,7 +230,7 @@ gnutls_Recv( void *p_session, void *buf, int i_length ) * needed, 2 if more would-be blocking send is required. *****************************************************************************/ static int -gnutls_SessionContinueHandshake( tls_session_t *p_session) +gnutls_ContinueHandshake( tls_session_t *p_session) { tls_session_sys_t *p_sys; int val; @@ -211,7 +255,51 @@ gnutls_SessionContinueHandshake( tls_session_t *p_session) } static int -gnutls_SessionHandshake( tls_session_t *p_session, int fd, +gnutls_HandshakeAndValidate( tls_session_t *p_session ) +{ + int val; + + val = gnutls_ContinueHandshake( p_session ); + if( val == 0 ) + { + int status; + + val = gnutls_certificate_verify_peers2( ((tls_session_sys_t *) + (p_session->p_sys))->session, + &status ); + + if( val ) + { + msg_Err( p_session, "TLS certificate verification failed : %s", + gnutls_strerror( val ) ); + p_session->pf_close( p_session ); + return -1; + } + + if( status ) + { + msg_Warn( p_session, "TLS session : access denied" ); + if( status & GNUTLS_CERT_INVALID ) + msg_Dbg( p_session, "certificate could not be verified" ); + if( status & GNUTLS_CERT_REVOKED ) + msg_Dbg( p_session, "certificate was revoked" ); + if( status & GNUTLS_CERT_SIGNER_NOT_FOUND ) + msg_Dbg( p_session, "certificate's signer was not found" ); + if( status & GNUTLS_CERT_SIGNER_NOT_CA ) + msg_Dbg( p_session, "certificate's signer is not a CA" ); + p_session->pf_close( p_session ); + return -1; + } + + msg_Dbg( p_session, "TLS certificate verified" ); + return 0; + } + + return val; +} + +static int +gnutls_BeginHandshake( tls_session_t *p_session, int fd, const char *psz_hostname ) { tls_session_sys_t *p_sys; @@ -223,10 +311,9 @@ gnutls_SessionHandshake( tls_session_t *p_session, int fd, gnutls_server_name_set( p_sys->session, GNUTLS_NAME_DNS, psz_hostname, strlen( psz_hostname ) ); - return gnutls_SessionContinueHandshake( p_session ); + return p_session->pf_handshake2( p_session ); } - /***************************************************************************** * tls_SessionClose: ***************************************************************************** @@ -253,12 +340,81 @@ static void gnutls_ClientDelete( tls_session_t *p_session ) { /* On the client-side, credentials are re-allocated per session */ - gnutls_certificate_free_credentials( ((tls_client_sys_t *) - (p_session->p_sys))->x509_cred ); + 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 ); } +inline int +is_regular( const char *psz_filename ) +{ +#ifdef HAVE_SYS_STAT_H + struct stat st; + + return ( stat( psz_filename, &st ) == 0 ) + && S_ISREG( st.st_mode ); +#else + return 1; +#endif +} + + +static int +gnutls_AddCADirectory( vlc_object_t *p_this, + gnutls_certificate_credentials cred, + const char *psz_dirname ) +{ + DIR* dir; + struct dirent *p_ent; + int i_len; + + if( *psz_dirname == '\0' ) + psz_dirname = "."; + + dir = opendir( psz_dirname ); + if( dir == NULL ) + { + msg_Warn( p_this, "Cannot open directory (%s) : %s", psz_dirname, + strerror( errno ) ); + return VLC_EGENERIC; + } + + i_len = strlen( psz_dirname ) + 2; + + while( ( p_ent = readdir( dir ) ) != NULL ) + { + char *psz_filename; + + psz_filename = (char *)malloc( i_len + strlen( p_ent->d_name ) ); + if( psz_filename == NULL ) + return VLC_ENOMEM; + + sprintf( psz_filename, "%s/%s", psz_dirname, p_ent->d_name ); + /* we neglect the race condition here - not security sensitive */ + if( is_regular( psz_filename ) ) + { + int i; + + i = gnutls_certificate_set_x509_trust_file( cred, psz_filename, + GNUTLS_X509_FMT_PEM ); + if( i < 0 ) + { + msg_Warn( p_this, "Cannot add trusted CA (%s) : %s", + psz_filename, gnutls_strerror( i ) ); + } + } + free( psz_filename ); + } + + closedir( dir ); + return VLC_SUCCESS; +} + /***************************************************************************** * tls_ClientCreate: ***************************************************************************** @@ -291,8 +447,7 @@ gnutls_ClientCreate( tls_t *p_tls ) 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_SessionHandshake; - p_session->pf_handshake2 = gnutls_SessionContinueHandshake; + p_session->pf_handshake = gnutls_BeginHandshake; p_session->pf_close = gnutls_ClientDelete; p_sys->session.b_handshaked = VLC_FALSE; @@ -307,21 +462,30 @@ gnutls_ClientCreate( tls_t *p_tls ) goto error; } -#if 0 - if( psz_ca_path != NULL ) + if( get_Bool( p_tls, "tls-check-cert" ) ) { - i_val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred, - psz_ca_path, - GNUTLS_X509_FMT_PEM ); - if( i_val != 0 ) + /* FIXME: support for changing path/using multiple paths */ + char *psz_path; + const char *psz_homedir; + + psz_homedir = p_tls->p_vlc->psz_homedir; + psz_path = (char *)malloc( strlen( psz_homedir ) + + sizeof( CONFIG_DIR ) + 5 ); + if( psz_path == NULL ) { - msg_Err( p_tls, "Cannot add trusted CA (%s) : %s", psz_ca_path, - gnutls_strerror( i_val ) ); gnutls_certificate_free_credentials( p_sys->x509_cred ); goto error; } + + sprintf( psz_path, "%s/"CONFIG_DIR"/ssl", psz_homedir ); + gnutls_AddCADirectory( (vlc_object_t *)p_session, p_sys->x509_cred, + psz_path ); + + p_session->pf_handshake2 = gnutls_HandshakeAndValidate; } -#endif + else + p_session->pf_handshake2 = gnutls_ContinueHandshake; + i_val = gnutls_init( &p_sys->session.session, GNUTLS_CLIENT ); if( i_val != 0 ) { @@ -482,6 +646,7 @@ static tls_session_t * gnutls_ServerSessionPrepare( tls_server_t *p_server ) { tls_session_t *p_session; + tls_server_sys_t *p_server_sys; gnutls_session session; int i_val; @@ -498,11 +663,12 @@ gnutls_ServerSessionPrepare( tls_server_t *p_server ) vlc_object_attach( p_session, p_server ); + p_server_sys = (tls_server_sys_t *)p_server->p_sys; 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_SessionHandshake; - p_session->pf_handshake2 = gnutls_SessionContinueHandshake; + p_session->pf_handshake = gnutls_BeginHandshake; + p_session->pf_handshake2 = p_server_sys->pf_handshake2; p_session->pf_close = gnutls_SessionClose; ((tls_session_sys_t *)p_session->p_sys)->b_handshaked = VLC_FALSE; @@ -527,8 +693,7 @@ gnutls_ServerSessionPrepare( tls_server_t *p_server ) } i_val = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE, - ((tls_server_sys_t *)(p_server->p_sys)) - ->x509_cred ); + p_server_sys->x509_cred ); if( i_val < 0 ) { msg_Err( p_server, "Cannot set TLS session credentials : %s", @@ -537,9 +702,8 @@ gnutls_ServerSessionPrepare( tls_server_t *p_server ) goto error; } - /* TODO: support for client authentication */ - /*gnutls_certificate_server_set_request( p_session->session, - GNUTLS_CERT_REQUEST ); */ + if( p_session->pf_handshake2 == gnutls_HandshakeAndValidate ) + gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUIRE ); gnutls_dh_set_prime_bits( session, get_Int( p_server, "dh-bits" ) ); @@ -570,16 +734,17 @@ static void gnutls_ServerDelete( tls_server_t *p_server ) { tls_server_sys_t *p_sys; - p_sys = (tls_server_sys_t *)p_server->p_sys; - gnutls_certificate_free_credentials( p_sys->x509_cred ); vlc_mutex_destroy( &p_sys->cache_lock ); + free( p_sys->p_cache ); vlc_object_detach( p_server ); vlc_object_destroy( p_server ); - free( p_sys->p_cache ); + /* all sessions depending on the server are now deinitialized */ + gnutls_certificate_free_credentials( p_sys->x509_cred ); + gnutls_dh_params_deinit( p_sys->dh_params ); free( p_sys ); } @@ -588,28 +753,30 @@ gnutls_ServerDelete( tls_server_t *p_server ) * tls_ServerAddCA: ***************************************************************************** * Adds one or more certificate authorities. - * TODO: we are not able to check the client credentials yet, so this function - * is pretty useless. * Returns -1 on error, 0 on success. *****************************************************************************/ static int gnutls_ServerAddCA( tls_server_t *p_server, const char *psz_ca_path ) { int val; + tls_server_sys_t *p_sys; - val = gnutls_certificate_set_x509_trust_file( ((tls_server_sys_t *) - (p_server->p_sys)) - ->x509_cred, + p_sys = (tls_server_sys_t *)(p_server->p_sys); + + val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred, psz_ca_path, GNUTLS_X509_FMT_PEM ); if( val < 0 ) { msg_Err( p_server, "Cannot add trusted CA (%s) : %s", psz_ca_path, gnutls_strerror( val ) ); - gnutls_ServerDelete( p_server ); return VLC_EGENERIC; } msg_Dbg( p_server, " %d trusted CA added (%s)", val, psz_ca_path ); + + /* enables peer's certificate verification */ + p_sys->pf_handshake2 = gnutls_HandshakeAndValidate; + return VLC_SUCCESS; } @@ -633,7 +800,6 @@ gnutls_ServerAddCRL( tls_server_t *p_server, const char *psz_crl_path ) { msg_Err( p_server, "Cannot add CRL (%s) : %s", psz_crl_path, gnutls_strerror( val ) ); - gnutls_ServerDelete( p_server ); return VLC_EGENERIC; } msg_Dbg( p_server, "%d CRL added (%s)", val, psz_crl_path ); @@ -687,6 +853,9 @@ gnutls_ServerCreate( tls_t *p_tls, const char *psz_cert_path, p_server->pf_add_CRL = gnutls_ServerAddCRL; p_server->pf_session_prepare = gnutls_ServerSessionPrepare; + /* No certificate validation by default */ + p_sys->pf_handshake2 = gnutls_ContinueHandshake; + /* FIXME: check for errors */ vlc_mutex_init( p_server, &p_sys->cache_lock ); @@ -805,8 +974,8 @@ Open( vlc_object_t *p_this ) vlc_value_t lock, count; - var_Create( p_this->p_libvlc, "tls_mutex", VLC_VAR_MUTEX ); - var_Get( p_this->p_libvlc, "tls_mutex", &lock ); + var_Create( p_this->p_libvlc, "gnutls_mutex", VLC_VAR_MUTEX ); + var_Get( p_this->p_libvlc, "gnutls_mutex", &lock ); vlc_mutex_lock( lock.p_address ); /* Initialize GnuTLS only once */ @@ -815,23 +984,30 @@ Open( vlc_object_t *p_this ) if( count.i_int == 0) { + const char *psz_version; + __p_gcry_data = VLC_OBJECT( p_this->p_vlc ); gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_vlc); if( gnutls_global_init( ) ) { - msg_Warn( p_this, "cannot initialize GNUTLS" ); + msg_Warn( p_this, "cannot initialize GnuTLS" ); vlc_mutex_unlock( lock.p_address ); return VLC_EGENERIC; } - if( gnutls_check_version( "1.0.0" ) == NULL ) + /* + * FIXME: in fact, we currently depends on 1.0.17, but it breaks on + * Debian which as a patched 1.0.16 (which we can use). + */ + psz_version = gnutls_check_version( "1.0.16" ); + if( psz_version == NULL ) { gnutls_global_deinit( ); vlc_mutex_unlock( lock.p_address ); - msg_Err( p_this, "unsupported GNUTLS version" ); + msg_Err( p_this, "unsupported GnuTLS version" ); return VLC_EGENERIC; } - msg_Dbg( p_this, "GNUTLS initialized" ); + msg_Dbg( p_this, "GnuTLS v%s initialized", psz_version ); } count.i_int++; @@ -867,8 +1043,8 @@ Close( vlc_object_t *p_this ) if( count.i_int == 0 ) { gnutls_global_deinit( ); - msg_Dbg( p_this, "GNUTLS deinitialized" ); + msg_Dbg( p_this, "GnuTLS deinitialized" ); } - vlc_mutex_unlock( lock.p_address); + vlc_mutex_unlock( lock.p_address ); }