]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
Client-side server credentials verification
[vlc] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * tls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2005 VideoLAN
5  * $Id$
6  *
7  * Authors: Remi Denis-Courmont <rem # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*
25  * TODO:
26  * - fix FIXMEs
27  */
28
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <vlc/vlc.h>
36
37 #include <sys/types.h>
38 #include <errno.h>
39 #ifdef HAVE_DIRENT_H
40 # include <dirent.h>
41 #endif
42 #ifdef HAVE_SYS_STAT_H
43 # include <sys/stat.h>
44 # ifdef HAVE_UNISTD_H
45 #  include <unistd.h>
46 # endif
47 #endif
48
49
50 #include "vlc_tls.h"
51
52 #include <gcrypt.h>
53 #include <gnutls/gnutls.h>
54
55 #define DH_BITS             1024
56 #define CACHE_EXPIRATION    3600
57 #define CACHE_SIZE          64
58
59 /*****************************************************************************
60  * Module descriptor
61  *****************************************************************************/
62 static int  Open ( vlc_object_t * );
63 static void Close( vlc_object_t * );
64
65 #define DH_BITS_TEXT N_("Diffie-Hellman prime bits")
66 #define DH_BITS_LONGTEXT N_( \
67     "Allows you to modify the Diffie-Hellman prime's number of bits " \
68     "(used for TLS or SSL-based server-side encryption)." )
69
70 #define CACHE_EXPIRATION_TEXT N_("Expiration time for resumed TLS sessions")
71 #define CACHE_EXPIRATION_LONGTEXT N_( \
72     "Defines the delay before resumed TLS sessions will be expired " \
73     "(in seconds)." )
74
75 #define CACHE_SIZE_TEXT N_("Number of resumed TLS sessions")
76 #define CACHE_SIZE_LONGTEXT N_( \
77     "Allows you to modify the maximum number of resumed TLS sessions that " \
78     "the cache will hold." )
79
80 #define CHECK_CERT_TEXT N_("Check TLS/SSL server certificate validity")
81 #define CHECK_CERT_LONGTEXT N_( \
82     "Ensures that server certificate is valid " \
83     "(ie. signed by an approved Certificate Authority)." )
84
85 #define CHECK_HOSTNAME_TEXT N_("Check TLS/SSL server hostname in certificate")
86 #define CHECK_HOSTNAME_LONGTEXT N_( \
87     "Ensures that server hostname in certificate match requested host name." )
88
89 vlc_module_begin();
90     set_shortname( "GnuTLS" );
91     set_description( _("GnuTLS TLS encryption layer") );
92     set_capability( "tls", 1 );
93     set_callbacks( Open, Close );
94     set_category( CAT_ADVANCED );
95     set_subcategory( SUBCAT_ADVANCED_MISC );
96
97     add_bool( "tls-check-cert", VLC_FALSE, NULL, CHECK_CERT_TEXT,
98               CHECK_CERT_LONGTEXT, VLC_FALSE );
99 #if 0
100     add_bool( "tls-check-hostname", VLC_FALSE, NULL, CHECK_HOSTNAME_TEXT,
101               CHECK_HOSTNAME_LONGTEXT, VLC_FALSE );
102 #endif
103
104     add_integer( "dh-bits", DH_BITS, NULL, DH_BITS_TEXT,
105                  DH_BITS_LONGTEXT, VLC_TRUE );
106     add_integer( "tls-cache-expiration", CACHE_EXPIRATION, NULL,
107                  CACHE_EXPIRATION_TEXT, CACHE_EXPIRATION_LONGTEXT, VLC_TRUE );
108     add_integer( "tls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
109                  CACHE_SIZE_LONGTEXT, VLC_TRUE );
110 vlc_module_end();
111
112
113 #define MAX_SESSION_ID    32
114 #define MAX_SESSION_DATA  1024
115
116 typedef struct saved_session_t
117 {
118     char id[MAX_SESSION_ID];
119     char data[MAX_SESSION_DATA];
120
121     unsigned i_idlen;
122     unsigned i_datalen;
123 } saved_session_t;
124
125
126 typedef struct tls_server_sys_t
127 {
128     gnutls_certificate_credentials  x509_cred;
129     gnutls_dh_params                dh_params;
130
131     struct saved_session_t          *p_cache;
132     struct saved_session_t          *p_store;
133     int                             i_cache_size;
134     vlc_mutex_t                     cache_lock;
135
136     int                             (*pf_handshake2)( tls_session_t * );
137 } tls_server_sys_t;
138
139
140 typedef struct tls_session_sys_t
141 {
142     gnutls_session  session;
143     vlc_bool_t      b_handshaked;
144 } tls_session_sys_t;
145
146
147 typedef struct tls_client_sys_t
148 {
149     struct tls_session_sys_t       session;
150     gnutls_certificate_credentials x509_cred;
151 } tls_client_sys_t;
152
153
154 static int
155 _get_Int( vlc_object_t *p_this, const char *var )
156 {
157     vlc_value_t value;
158
159     if( var_Get( p_this, var, &value ) != VLC_SUCCESS )
160     {
161         var_Create( p_this, var, VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
162         var_Get( p_this, var, &value );
163     }
164
165     return value.i_int;
166 }
167
168 static int
169 _get_Bool( vlc_object_t *p_this, const char *var )
170 {
171     vlc_value_t value;
172
173     if( var_Get( p_this, var, &value ) != VLC_SUCCESS )
174     {
175         var_Create( p_this, var, VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
176         var_Get( p_this, var, &value );
177     }
178
179     return value.b_bool;
180 }
181
182 #define get_Int( a, b ) _get_Int( (vlc_object_t *)(a), (b) )
183 #define get_Bool( a, b ) _get_Bool( (vlc_object_t *)(a), (b) )
184
185
186 /*****************************************************************************
187  * tls_Send:
188  *****************************************************************************
189  * Sends data through a TLS session.
190  *****************************************************************************/
191 static int
192 gnutls_Send( void *p_session, const void *buf, int i_length )
193 {
194     int val;
195     tls_session_sys_t *p_sys;
196
197     p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
198
199     val = gnutls_record_send( p_sys->session, buf, i_length );
200     /* TODO: handle fatal error */
201     return val < 0 ? -1 : val;
202 }
203
204
205 /*****************************************************************************
206  * tls_Recv:
207  *****************************************************************************
208  * Receives data through a TLS session.
209  *****************************************************************************/
210 static int
211 gnutls_Recv( void *p_session, void *buf, int i_length )
212 {
213     int val;
214     tls_session_sys_t *p_sys;
215
216     p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
217
218     val = gnutls_record_recv( p_sys->session, buf, i_length );
219     /* TODO: handle fatal error */
220     return val < 0 ? -1 : val;
221 }
222
223
224 /*****************************************************************************
225  * tls_Session(Continue)?Handshake:
226  *****************************************************************************
227  * Establishes TLS session with a peer through socket <fd>.
228  * Returns -1 on error (you need not and must not call tls_SessionClose)
229  * 0 on succesful handshake completion, 1 if more would-be blocking recv is
230  * needed, 2 if more would-be blocking send is required.
231  *****************************************************************************/
232 static int
233 gnutls_ContinueHandshake( tls_session_t *p_session)
234 {
235     tls_session_sys_t *p_sys;
236     int val;
237
238     p_sys = (tls_session_sys_t *)(p_session->p_sys);
239
240      /* TODO: handle fatal error */
241     val = gnutls_handshake( p_sys->session );
242     if( ( val == GNUTLS_E_AGAIN ) || ( val == GNUTLS_E_INTERRUPTED ) )
243         return 1 + gnutls_record_get_direction( p_sys->session );
244
245     if( val < 0 )
246     {
247         msg_Err( p_session, "TLS handshake failed : %s",
248                  gnutls_strerror( val ) );
249         p_session->pf_close( p_session );
250         return -1;
251     }
252
253     p_sys->b_handshaked = VLC_TRUE;
254     return 0;
255 }
256
257 static int
258 gnutls_HandshakeAndValidate( tls_session_t *p_session )
259 {
260     int val;
261
262     val = gnutls_ContinueHandshake( p_session );
263     if( val == 0 )
264     {
265         int status;
266
267         val = gnutls_certificate_verify_peers2( ((tls_session_sys_t *)
268                                                 (p_session->p_sys))->session,
269                                                 &status );
270
271         if( val )
272         {
273             msg_Err( p_session, "TLS certificate verification failed : %s",
274                      gnutls_strerror( val ) );
275             p_session->pf_close( p_session );
276             return -1;
277         }
278
279         if( status )
280         {
281             msg_Warn( p_session, "TLS session : access denied" );
282             if( status & GNUTLS_CERT_INVALID )
283                 msg_Dbg( p_session, "certificate could not be verified" );
284             if( status & GNUTLS_CERT_REVOKED )
285                 msg_Dbg( p_session, "certificate was revoked" );
286             if( status & GNUTLS_CERT_SIGNER_NOT_FOUND )
287                 msg_Dbg( p_session, "certificate's signer was not found" );
288             if( status & GNUTLS_CERT_SIGNER_NOT_CA )
289                 msg_Dbg( p_session, "certificate's signer is not a CA" );
290             p_session->pf_close( p_session );
291             return -1;
292         }
293
294         msg_Dbg( p_session, "TLS certificate verified" );
295         return 0;
296     }
297
298     return val;
299 }
300
301 static int
302 gnutls_BeginHandshake( tls_session_t *p_session, int fd,
303                          const char *psz_hostname )
304 {
305     tls_session_sys_t *p_sys;
306
307     p_sys = (tls_session_sys_t *)(p_session->p_sys);
308
309     gnutls_transport_set_ptr (p_sys->session, (gnutls_transport_ptr)fd);
310     if( psz_hostname != NULL )
311         gnutls_server_name_set( p_sys->session, GNUTLS_NAME_DNS, psz_hostname,
312                                 strlen( psz_hostname ) );
313
314     return p_session->pf_handshake2( p_session );
315 }
316
317 /*****************************************************************************
318  * tls_SessionClose:
319  *****************************************************************************
320  * Terminates TLS session and releases session data.
321  *****************************************************************************/
322 static void
323 gnutls_SessionClose( tls_session_t *p_session )
324 {
325     tls_session_sys_t *p_sys;
326
327     p_sys = (tls_session_sys_t *)(p_session->p_sys);
328
329     if( p_sys->b_handshaked == VLC_TRUE )
330         gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
331     gnutls_deinit( p_sys->session );
332
333     vlc_object_detach( p_session );
334     vlc_object_destroy( p_session );
335
336     free( p_sys );
337 }
338
339 static void
340 gnutls_ClientDelete( tls_session_t *p_session )
341 {
342     /* On the client-side, credentials are re-allocated per session */
343     gnutls_certificate_credentials x509_cred =
344                         ((tls_client_sys_t *)(p_session->p_sys))->x509_cred;
345
346     gnutls_SessionClose( p_session );
347
348     /* credentials must be free'd *after* gnutls_deinit() */
349     gnutls_certificate_free_credentials( x509_cred );
350 }
351
352
353 inline int
354 is_regular( const char *psz_filename )
355 {
356 #ifdef HAVE_SYS_STAT_H
357     struct stat st;
358
359     return ( stat( psz_filename, &st ) == 0 )
360         && S_ISREG( st.st_mode );
361 #else
362     return 1;
363 #endif
364 }
365
366
367 static int
368 gnutls_AddCADirectory( vlc_object_t *p_this,
369                        gnutls_certificate_credentials cred,
370                        const char *psz_dirname )
371 {
372     DIR* dir;
373     struct dirent *p_ent;
374     int i_len;
375
376     if( *psz_dirname == '\0' )
377         psz_dirname = ".";
378
379     dir = opendir( psz_dirname );
380     if( dir == NULL )
381     {
382         msg_Warn( p_this, "Cannot open directory (%s) : %s", psz_dirname,
383                   strerror( errno ) );
384         return VLC_EGENERIC;
385     }
386
387     i_len = strlen( psz_dirname ) + 2;
388
389     while( ( p_ent = readdir( dir ) ) != NULL )
390     {
391         char *psz_filename;
392
393         psz_filename = (char *)malloc( i_len + strlen( p_ent->d_name ) );
394         if( psz_filename == NULL )
395             return VLC_ENOMEM;
396
397         sprintf( psz_filename, "%s/%s", psz_dirname, p_ent->d_name );
398         /* we neglect the race condition here - not security sensitive */
399         if( is_regular( psz_filename ) )
400         {
401             int i;
402
403             i = gnutls_certificate_set_x509_trust_file( cred, psz_filename,
404                                                         GNUTLS_X509_FMT_PEM );
405             if( i < 0 )
406             {
407                 msg_Warn( p_this, "Cannot add trusted CA (%s) : %s",
408                           psz_filename, gnutls_strerror( i ) );
409             }
410         }
411         free( psz_filename );
412     }
413
414     closedir( dir );
415     return VLC_SUCCESS;
416 }
417
418 /*****************************************************************************
419  * tls_ClientCreate:
420  *****************************************************************************
421  * Initializes client-side TLS session data.
422  *****************************************************************************/
423 static tls_session_t *
424 gnutls_ClientCreate( tls_t *p_tls )
425 {
426     tls_session_t *p_session = NULL;
427     tls_client_sys_t *p_sys = NULL;
428     int i_val;
429     const int cert_type_priority[3] =
430     {
431         GNUTLS_CRT_X509,
432         0
433     };
434
435     p_sys = (tls_client_sys_t *)malloc( sizeof(struct tls_client_sys_t) );
436     if( p_sys == NULL )
437         return NULL;
438    
439     p_session = (struct tls_session_t *)vlc_object_create ( p_tls, sizeof(struct tls_session_t) );
440     if( p_session == NULL )
441     {
442         free( p_sys );
443         return NULL;
444     }
445
446     p_session->p_sys = p_sys;
447     p_session->sock.p_sys = p_session;
448     p_session->sock.pf_send = gnutls_Send;
449     p_session->sock.pf_recv = gnutls_Recv;
450     p_session->pf_handshake = gnutls_BeginHandshake;
451     p_session->pf_close = gnutls_ClientDelete;
452
453     p_sys->session.b_handshaked = VLC_FALSE;
454
455     vlc_object_attach( p_session, p_tls );
456
457     i_val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
458     if( i_val != 0 )
459     {
460         msg_Err( p_tls, "Cannot allocate X509 credentials : %s",
461                  gnutls_strerror( i_val ) );
462         goto error;
463     }
464
465     if( get_Bool( p_tls, "tls-check-cert" ) )
466     {
467         /* FIXME: support for changing path/using multiple paths */
468         char *psz_path;
469         const char *psz_homedir;
470
471         psz_homedir = p_tls->p_vlc->psz_homedir;
472         psz_path = (char *)malloc( strlen( psz_homedir )
473                                    + sizeof( CONFIG_DIR ) + 5 );
474         if( psz_path == NULL )
475         {
476             gnutls_certificate_free_credentials( p_sys->x509_cred );
477             goto error;
478         }
479
480         sprintf( psz_path, "%s/"CONFIG_DIR"/ssl", psz_homedir );
481         gnutls_AddCADirectory( (vlc_object_t *)p_session, p_sys->x509_cred,
482                                psz_path );
483
484         p_session->pf_handshake2 = gnutls_HandshakeAndValidate;
485     }
486     else
487         p_session->pf_handshake2 = gnutls_ContinueHandshake;
488
489     i_val = gnutls_init( &p_sys->session.session, GNUTLS_CLIENT );
490     if( i_val != 0 )
491     {
492         msg_Err( p_tls, "Cannot initialize TLS session : %s",
493                  gnutls_strerror( i_val ) );
494         gnutls_certificate_free_credentials( p_sys->x509_cred );
495         goto error;
496     }
497
498     i_val = gnutls_set_default_priority( p_sys->session.session );
499     if( i_val < 0 )
500     {
501         msg_Err( p_tls, "Cannot set ciphers priorities : %s",
502                  gnutls_strerror( i_val ) );
503         gnutls_deinit( p_sys->session.session );
504         gnutls_certificate_free_credentials( p_sys->x509_cred );
505         goto error;
506     }
507
508     i_val = gnutls_certificate_type_set_priority( p_sys->session.session,
509                                                   cert_type_priority );
510     if( i_val < 0 )
511     {
512         msg_Err( p_tls, "Cannot set certificate type priorities : %s",
513                  gnutls_strerror( i_val ) );
514         gnutls_deinit( p_sys->session.session );
515         gnutls_certificate_free_credentials( p_sys->x509_cred );
516         goto error;
517     }
518
519     i_val = gnutls_credentials_set( p_sys->session.session,
520                                     GNUTLS_CRD_CERTIFICATE,
521                                     p_sys->x509_cred );
522     if( i_val < 0 )
523     {
524         msg_Err( p_tls, "Cannot set TLS session credentials : %s",
525                  gnutls_strerror( i_val ) );
526         gnutls_deinit( p_sys->session.session );
527         gnutls_certificate_free_credentials( p_sys->x509_cred );
528         goto error;
529     }
530
531     return p_session;
532
533 error:
534     vlc_object_detach( p_session );
535     vlc_object_destroy( p_session );
536     free( p_sys );
537
538     return NULL;
539 }
540
541
542 /*****************************************************************************
543  * TLS session resumption callbacks
544  *****************************************************************************/
545 static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
546 {
547     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
548
549     if( ( p_sys->i_cache_size == 0 )
550      || ( key.size > MAX_SESSION_ID )
551      || ( data.size > MAX_SESSION_DATA ) )
552         return -1;
553
554     vlc_mutex_lock( &p_sys->cache_lock );
555
556     memcpy( p_sys->p_store->id, key.data, key.size);
557     memcpy( p_sys->p_store->data, data.data, data.size );
558     p_sys->p_store->i_idlen = key.size;
559     p_sys->p_store->i_datalen = data.size;
560
561     p_sys->p_store++;
562     if( ( p_sys->p_store - p_sys->p_cache ) == p_sys->i_cache_size )
563         p_sys->p_store = p_sys->p_cache;
564
565     vlc_mutex_unlock( &p_sys->cache_lock );
566
567     return 0;
568 }
569
570
571 static const gnutls_datum err_datum = { NULL, 0 };
572
573 static gnutls_datum cb_fetch( void *p_server, gnutls_datum key )
574 {
575     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
576     saved_session_t *p_session, *p_end;
577
578     p_session = p_sys->p_cache;
579     p_end = p_session + p_sys->i_cache_size;
580
581     vlc_mutex_lock( &p_sys->cache_lock );
582
583     while( p_session < p_end )
584     {
585         if( ( p_session->i_idlen == key.size )
586          && !memcmp( p_session->id, key.data, key.size ) )
587         {
588             gnutls_datum data;
589
590             data.size = p_session->i_datalen;
591
592             data.data = gnutls_malloc( data.size );
593             if( data.data == NULL )
594             {
595                 vlc_mutex_unlock( &p_sys->cache_lock );
596                 return err_datum;
597             }
598
599             memcpy( data.data, p_session->data, data.size );
600             vlc_mutex_unlock( &p_sys->cache_lock );
601             return data;
602         }
603         p_session++;
604     }
605
606     vlc_mutex_unlock( &p_sys->cache_lock );
607
608     return err_datum;
609 }
610
611
612 static int cb_delete( void *p_server, gnutls_datum key )
613 {
614     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
615     saved_session_t *p_session, *p_end;
616
617     p_session = p_sys->p_cache;
618     p_end = p_session + p_sys->i_cache_size;
619
620     vlc_mutex_lock( &p_sys->cache_lock );
621
622     while( p_session < p_end )
623     {
624         if( ( p_session->i_idlen == key.size )
625          && !memcmp( p_session->id, key.data, key.size ) )
626         {
627             p_session->i_datalen = p_session->i_idlen = 0;
628             vlc_mutex_unlock( &p_sys->cache_lock );
629             return 0;
630         }
631         p_session++;
632     }
633
634     vlc_mutex_unlock( &p_sys->cache_lock );
635
636     return -1;
637 }
638
639
640 /*****************************************************************************
641  * tls_ServerSessionPrepare:
642  *****************************************************************************
643  * Initializes server-side TLS session data.
644  *****************************************************************************/
645 static tls_session_t *
646 gnutls_ServerSessionPrepare( tls_server_t *p_server )
647 {
648     tls_session_t *p_session;
649     tls_server_sys_t *p_server_sys;
650     gnutls_session session;
651     int i_val;
652
653     p_session = vlc_object_create( p_server, sizeof (struct tls_session_t) );
654     if( p_session == NULL )
655         return NULL;
656     
657     p_session->p_sys = malloc( sizeof(struct tls_session_sys_t) );
658     if( p_session->p_sys == NULL )
659     {
660         vlc_object_destroy( p_session );
661         return NULL;
662     }
663
664     vlc_object_attach( p_session, p_server );
665
666     p_server_sys = (tls_server_sys_t *)p_server->p_sys;
667     p_session->sock.p_sys = p_session;
668     p_session->sock.pf_send = gnutls_Send;
669     p_session->sock.pf_recv = gnutls_Recv;
670     p_session->pf_handshake = gnutls_BeginHandshake;
671     p_session->pf_handshake2 = p_server_sys->pf_handshake2;
672     p_session->pf_close = gnutls_SessionClose;
673
674     ((tls_session_sys_t *)p_session->p_sys)->b_handshaked = VLC_FALSE;
675
676     i_val = gnutls_init( &session, GNUTLS_SERVER );
677     if( i_val != 0 )
678     {
679         msg_Err( p_server, "Cannot initialize TLS session : %s",
680                  gnutls_strerror( i_val ) );
681         goto error;
682     }
683
684     ((tls_session_sys_t *)p_session->p_sys)->session = session;
685
686     i_val = gnutls_set_default_priority( session );
687     if( i_val < 0 )
688     {
689         msg_Err( p_server, "Cannot set ciphers priorities : %s",
690                  gnutls_strerror( i_val ) );
691         gnutls_deinit( session );
692         goto error;
693     }
694
695     i_val = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE,
696                                     p_server_sys->x509_cred );
697     if( i_val < 0 )
698     {
699         msg_Err( p_server, "Cannot set TLS session credentials : %s",
700                  gnutls_strerror( i_val ) );
701         gnutls_deinit( session );
702         goto error;
703     }
704
705     if( p_session->pf_handshake2 == gnutls_HandshakeAndValidate )
706         gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUIRE );
707
708     gnutls_dh_set_prime_bits( session, get_Int( p_server, "dh-bits" ) );
709
710     /* Session resumption support */
711     gnutls_db_set_cache_expiration( session, get_Int( p_server,
712                                     "tls-cache-expiration" ) );
713     gnutls_db_set_retrieve_function( session, cb_fetch );
714     gnutls_db_set_remove_function( session, cb_delete );
715     gnutls_db_set_store_function( session, cb_store );
716     gnutls_db_set_ptr( session, p_server );
717
718     return p_session;
719
720 error:
721     free( p_session->p_sys );
722     vlc_object_detach( p_session );
723     vlc_object_destroy( p_session );
724     return NULL;
725 }
726
727
728 /*****************************************************************************
729  * tls_ServerDelete:
730  *****************************************************************************
731  * Releases data allocated with tls_ServerCreate.
732  *****************************************************************************/
733 static void
734 gnutls_ServerDelete( tls_server_t *p_server )
735 {
736     tls_server_sys_t *p_sys;
737     p_sys = (tls_server_sys_t *)p_server->p_sys;
738
739     vlc_mutex_destroy( &p_sys->cache_lock );
740     free( p_sys->p_cache );
741
742     vlc_object_detach( p_server );
743     vlc_object_destroy( p_server );
744
745     /* all sessions depending on the server are now deinitialized */
746     gnutls_certificate_free_credentials( p_sys->x509_cred );
747     gnutls_dh_params_deinit( p_sys->dh_params );
748     free( p_sys );
749 }
750
751
752 /*****************************************************************************
753  * tls_ServerAddCA:
754  *****************************************************************************
755  * Adds one or more certificate authorities.
756  * Returns -1 on error, 0 on success.
757  *****************************************************************************/
758 static int
759 gnutls_ServerAddCA( tls_server_t *p_server, const char *psz_ca_path )
760 {
761     int val;
762     tls_server_sys_t *p_sys;
763
764     p_sys = (tls_server_sys_t *)(p_server->p_sys);
765
766     val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred,
767                                                   psz_ca_path,
768                                                   GNUTLS_X509_FMT_PEM );
769     if( val < 0 )
770     {
771         msg_Err( p_server, "Cannot add trusted CA (%s) : %s", psz_ca_path,
772                  gnutls_strerror( val ) );
773         return VLC_EGENERIC;
774     }
775     msg_Dbg( p_server, " %d trusted CA added (%s)", val, psz_ca_path );
776
777     /* enables peer's certificate verification */
778     p_sys->pf_handshake2 = gnutls_HandshakeAndValidate;
779
780     return VLC_SUCCESS;
781 }
782
783
784 /*****************************************************************************
785  * tls_ServerAddCRL:
786  *****************************************************************************
787  * Adds a certificates revocation list to be sent to TLS clients.
788  * Returns -1 on error, 0 on success.
789  *****************************************************************************/
790 static int
791 gnutls_ServerAddCRL( tls_server_t *p_server, const char *psz_crl_path )
792 {
793     int val;
794
795     val = gnutls_certificate_set_x509_crl_file( ((tls_server_sys_t *)
796                                                 (p_server->p_sys))->x509_cred,
797                                                 psz_crl_path,
798                                                 GNUTLS_X509_FMT_PEM );
799     if( val < 0 )
800     {
801         msg_Err( p_server, "Cannot add CRL (%s) : %s", psz_crl_path,
802                  gnutls_strerror( val ) );
803         return VLC_EGENERIC;
804     }
805     msg_Dbg( p_server, "%d CRL added (%s)", val, psz_crl_path );
806     return VLC_SUCCESS;
807 }
808     
809
810 /*****************************************************************************
811  * tls_ServerCreate:
812  *****************************************************************************
813  * Allocates a whole server's TLS credentials.
814  * Returns NULL on error.
815  *****************************************************************************/
816 static tls_server_t *
817 gnutls_ServerCreate( tls_t *p_tls, const char *psz_cert_path,
818                      const char *psz_key_path )
819 {
820     tls_server_t *p_server;
821     tls_server_sys_t *p_sys;
822     int val;
823
824     msg_Dbg( p_tls, "Creating TLS server" );
825
826     p_sys = (tls_server_sys_t *)malloc( sizeof(struct tls_server_sys_t) );
827     if( p_sys == NULL )
828         return NULL;
829
830     p_sys->i_cache_size = get_Int( p_tls, "tls-cache-size" );
831     p_sys->p_cache = (struct saved_session_t *)calloc( p_sys->i_cache_size,
832                                            sizeof( struct saved_session_t ) );
833     if( p_sys->p_cache == NULL )
834     {
835         free( p_sys );
836         return NULL;
837     }
838     p_sys->p_store = p_sys->p_cache;
839
840     p_server = vlc_object_create( p_tls, sizeof(struct tls_server_t) );
841     if( p_server == NULL )
842     {
843         free( p_sys->p_cache );
844         free( p_sys );
845         return NULL;
846     }
847
848     vlc_object_attach( p_server, p_tls );
849
850     p_server->p_sys = p_sys;
851     p_server->pf_delete = gnutls_ServerDelete;
852     p_server->pf_add_CA = gnutls_ServerAddCA;
853     p_server->pf_add_CRL = gnutls_ServerAddCRL;
854     p_server->pf_session_prepare = gnutls_ServerSessionPrepare;
855
856     /* No certificate validation by default */
857     p_sys->pf_handshake2 = gnutls_ContinueHandshake;
858
859     /* FIXME: check for errors */
860     vlc_mutex_init( p_server, &p_sys->cache_lock );
861
862     /* Sets server's credentials */
863     val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
864     if( val != 0 )
865     {
866         msg_Err( p_server, "Cannot allocate X509 credentials : %s",
867                  gnutls_strerror( val ) );
868         goto error;
869     }
870
871     val = gnutls_certificate_set_x509_key_file( p_sys->x509_cred,
872                                                 psz_cert_path, psz_key_path,
873                                                 GNUTLS_X509_FMT_PEM );
874     if( val < 0 )
875     {
876         msg_Err( p_server, "Cannot set certificate chain or private key : %s",
877                  gnutls_strerror( val ) );
878         gnutls_certificate_free_credentials( p_sys->x509_cred );
879         goto error;
880     }
881
882     /* FIXME:
883      * - regenerate these regularly
884      * - support other ciper suites
885      */
886     val = gnutls_dh_params_init( &p_sys->dh_params );
887     if( val >= 0 )
888     {
889         msg_Dbg( p_server, "Computing Diffie Hellman ciphers parameters" );
890         val = gnutls_dh_params_generate2( p_sys->dh_params,
891                                           get_Int( p_tls, "dh-bits" ) );
892     }
893     if( val < 0 )
894     {
895         msg_Err( p_server, "Cannot initialize DH cipher suites : %s",
896                  gnutls_strerror( val ) );
897         gnutls_certificate_free_credentials( p_sys->x509_cred );
898         goto error;
899     }
900     msg_Dbg( p_server, "Ciphers parameters computed" );
901
902     gnutls_certificate_set_dh_params( p_sys->x509_cred, p_sys->dh_params);
903
904     return p_server;
905
906 error:
907     vlc_mutex_destroy( &p_sys->cache_lock );
908     vlc_object_detach( p_server );
909     vlc_object_destroy( p_server );
910     free( p_sys );
911     return NULL;
912 }
913
914
915 /*****************************************************************************
916  * gcrypt thread option VLC implementation:
917  *****************************************************************************/
918 vlc_object_t *__p_gcry_data;
919
920 static int gcry_vlc_mutex_init( void **p_sys )
921 {
922     int i_val;
923     vlc_mutex_t *p_lock = (vlc_mutex_t *)malloc( sizeof( vlc_mutex_t ) );
924
925     if( p_lock == NULL)
926         return ENOMEM;
927
928     i_val = vlc_mutex_init( __p_gcry_data, p_lock );
929     if( i_val )
930         free( p_lock );
931     else
932         *p_sys = p_lock;
933     return i_val;
934 }
935
936 static int gcry_vlc_mutex_destroy( void **p_sys )
937 {
938     int i_val;
939     vlc_mutex_t *p_lock = (vlc_mutex_t *)*p_sys;
940
941     i_val = vlc_mutex_destroy( p_lock );
942     free( p_lock );
943     return i_val;
944 }
945
946 static int gcry_vlc_mutex_lock( void **p_sys )
947 {
948     return vlc_mutex_lock( (vlc_mutex_t *)*p_sys );
949 }
950
951 static int gcry_vlc_mutex_unlock( void **lock )
952 {
953     return vlc_mutex_unlock( (vlc_mutex_t *)*lock );
954 }
955
956 static struct gcry_thread_cbs gcry_threads_vlc =
957 {
958     GCRY_THREAD_OPTION_USER,
959     NULL,
960     gcry_vlc_mutex_init,
961     gcry_vlc_mutex_destroy,
962     gcry_vlc_mutex_lock,
963     gcry_vlc_mutex_unlock
964 };
965
966
967 /*****************************************************************************
968  * Module initialization
969  *****************************************************************************/
970 static int
971 Open( vlc_object_t *p_this )
972 {
973     tls_t *p_tls = (tls_t *)p_this;
974
975     vlc_value_t lock, count;
976
977     var_Create( p_this->p_libvlc, "gnutls_mutex", VLC_VAR_MUTEX );
978     var_Get( p_this->p_libvlc, "gnutls_mutex", &lock );
979     vlc_mutex_lock( lock.p_address );
980
981     /* Initialize GnuTLS only once */
982     var_Create( p_this->p_libvlc, "gnutls_count", VLC_VAR_INTEGER );
983     var_Get( p_this->p_libvlc, "gnutls_count", &count);
984
985     if( count.i_int == 0)
986     {
987         const char *psz_version;
988
989         __p_gcry_data = VLC_OBJECT( p_this->p_vlc );
990
991         gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_vlc);
992         if( gnutls_global_init( ) )
993         {
994             msg_Warn( p_this, "cannot initialize GnuTLS" );
995             vlc_mutex_unlock( lock.p_address );
996             return VLC_EGENERIC;
997         }
998         /*
999          * FIXME: in fact, we currently depends on 1.0.17, but it breaks on
1000          * Debian which as a patched 1.0.16 (which we can use).
1001          */
1002         psz_version = gnutls_check_version( "1.0.16" );
1003         if( psz_version == NULL )
1004         {
1005             gnutls_global_deinit( );
1006             vlc_mutex_unlock( lock.p_address );
1007             msg_Err( p_this, "unsupported GnuTLS version" );
1008             return VLC_EGENERIC;
1009         }
1010         msg_Dbg( p_this, "GnuTLS v%s initialized", psz_version );
1011     }
1012
1013     count.i_int++;
1014     var_Set( p_this->p_libvlc, "gnutls_count", count);
1015     vlc_mutex_unlock( lock.p_address );
1016
1017     p_tls->pf_server_create = gnutls_ServerCreate;
1018     p_tls->pf_client_create = gnutls_ClientCreate;
1019     return VLC_SUCCESS;
1020 }
1021
1022
1023 /*****************************************************************************
1024  * Module deinitialization
1025  *****************************************************************************/
1026 static void
1027 Close( vlc_object_t *p_this )
1028 {
1029     /*tls_t *p_tls = (tls_t *)p_this;
1030     tls_sys_t *p_sys = (tls_sys_t *)(p_this->p_sys);*/
1031
1032     vlc_value_t lock, count;
1033
1034     var_Create( p_this->p_libvlc, "gnutls_mutex", VLC_VAR_MUTEX );
1035     var_Get( p_this->p_libvlc, "gnutls_mutex", &lock );
1036     vlc_mutex_lock( lock.p_address );
1037
1038     var_Create( p_this->p_libvlc, "gnutls_count", VLC_VAR_INTEGER );
1039     var_Get( p_this->p_libvlc, "gnutls_count", &count);
1040     count.i_int--;
1041     var_Set( p_this->p_libvlc, "gnutls_count", count);
1042
1043     if( count.i_int == 0 )
1044     {
1045         gnutls_global_deinit( );
1046         msg_Dbg( p_this, "GnuTLS deinitialized" );
1047     }
1048
1049     vlc_mutex_unlock( lock.p_address );
1050 }