]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
- Work-around for IPv6 SSM with Winsock 2 from Olivier Levon
[vlc] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * gnutls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2006 Rémi Denis-Courmont
5  * $Id$
6  *
7  * Authors: Rémi 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 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 #include "charset.h"
52
53 #include <gcrypt.h>
54 #include <gnutls/gnutls.h>
55 #include <gnutls/x509.h>
56
57 #define DH_BITS             1024
58 #define CACHE_EXPIRATION    3600
59 #define CACHE_SIZE          64
60
61 /*****************************************************************************
62  * Module descriptor
63  *****************************************************************************/
64 static int  Open ( vlc_object_t * );
65 static void Close( vlc_object_t * );
66
67 #define DH_BITS_TEXT N_("Diffie-Hellman prime bits")
68 #define DH_BITS_LONGTEXT N_( \
69     "This allows you to modify the Diffie-Hellman prime's number of bits, " \
70     "used for TLS or SSL-based server-side encryption. This is generally " \
71     "not needed." )
72
73 #define CACHE_EXPIRATION_TEXT N_("Expiration time for resumed TLS sessions")
74 #define CACHE_EXPIRATION_LONGTEXT N_( \
75     "It is possible to cache the resumed TLS sessions. This is the expiration "\
76     "time of the sessions stored in this cache, in seconds." )
77
78 #define CACHE_SIZE_TEXT N_("Number of resumed TLS sessions")
79 #define CACHE_SIZE_LONGTEXT N_( \
80     "This is the maximum number of resumed TLS sessions that " \
81     "the cache will hold." )
82
83 #define CHECK_CERT_TEXT N_("Check TLS/SSL server certificate validity")
84 #define CHECK_CERT_LONGTEXT N_( \
85     "This ensures that the server certificate is valid " \
86     "(i.e. signed by an approved Certification Authority)." )
87
88 #define CHECK_HOSTNAME_TEXT N_("Check TLS/SSL server hostname in certificate")
89 #define CHECK_HOSTNAME_LONGTEXT N_( \
90     "This ensures that the server hostname in certificate matches the " \
91     "requested host name." )
92
93 vlc_module_begin();
94     set_shortname( "GnuTLS" );
95     set_description( _("GnuTLS TLS encryption layer") );
96     set_capability( "tls", 1 );
97     set_callbacks( Open, Close );
98     set_category( CAT_ADVANCED );
99     set_subcategory( SUBCAT_ADVANCED_MISC );
100
101     add_bool( "tls-check-cert", VLC_TRUE, NULL, CHECK_CERT_TEXT,
102               CHECK_CERT_LONGTEXT, VLC_FALSE );
103     add_bool( "tls-check-hostname", VLC_TRUE, NULL, CHECK_HOSTNAME_TEXT,
104               CHECK_HOSTNAME_LONGTEXT, VLC_FALSE );
105
106     add_integer( "gnutls-dh-bits", DH_BITS, NULL, DH_BITS_TEXT,
107                  DH_BITS_LONGTEXT, VLC_TRUE );
108     add_integer( "gnutls-cache-expiration", CACHE_EXPIRATION, NULL,
109                  CACHE_EXPIRATION_TEXT, CACHE_EXPIRATION_LONGTEXT, VLC_TRUE );
110     add_integer( "gnutls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
111                  CACHE_SIZE_LONGTEXT, VLC_TRUE );
112 vlc_module_end();
113
114
115 #define MAX_SESSION_ID    32
116 #define MAX_SESSION_DATA  1024
117
118 typedef struct saved_session_t
119 {
120     char id[MAX_SESSION_ID];
121     char data[MAX_SESSION_DATA];
122
123     unsigned i_idlen;
124     unsigned i_datalen;
125 } saved_session_t;
126
127
128 typedef struct tls_server_sys_t
129 {
130     gnutls_certificate_credentials  x509_cred;
131     gnutls_dh_params                dh_params;
132
133     struct saved_session_t          *p_cache;
134     struct saved_session_t          *p_store;
135     int                             i_cache_size;
136     vlc_mutex_t                     cache_lock;
137
138     int                             (*pf_handshake2)( tls_session_t * );
139 } tls_server_sys_t;
140
141
142 typedef struct tls_session_sys_t
143 {
144     gnutls_session  session;
145     char            *psz_hostname;
146     vlc_bool_t      b_handshaked;
147 } tls_session_sys_t;
148
149
150 typedef struct tls_client_sys_t
151 {
152     struct tls_session_sys_t       session;
153     gnutls_certificate_credentials x509_cred;
154 } tls_client_sys_t;
155
156
157 static int
158 _get_Int( vlc_object_t *p_this, const char *var )
159 {
160     vlc_value_t value;
161
162     if( var_Get( p_this, var, &value ) != VLC_SUCCESS )
163     {
164         var_Create( p_this, var, VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
165         var_Get( p_this, var, &value );
166     }
167
168     return value.i_int;
169 }
170
171 static int
172 _get_Bool( vlc_object_t *p_this, const char *var )
173 {
174     vlc_value_t value;
175
176     if( var_Get( p_this, var, &value ) != VLC_SUCCESS )
177     {
178         var_Create( p_this, var, VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
179         var_Get( p_this, var, &value );
180     }
181
182     return value.b_bool;
183 }
184
185 #define get_Int( a, b ) _get_Int( (vlc_object_t *)(a), (b) )
186 #define get_Bool( a, b ) _get_Bool( (vlc_object_t *)(a), (b) )
187
188
189 /**
190  * Sends data through a TLS session.
191  */
192 static int
193 gnutls_Send( void *p_session, const void *buf, int i_length )
194 {
195     int val;
196     tls_session_sys_t *p_sys;
197
198     p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
199
200     val = gnutls_record_send( p_sys->session, buf, i_length );
201     /* TODO: handle fatal error */
202     return val < 0 ? -1 : val;
203 }
204
205
206 /**
207  * Receives data through a TLS session.
208  */
209 static int
210 gnutls_Recv( void *p_session, void *buf, int i_length )
211 {
212     int val;
213     tls_session_sys_t *p_sys;
214
215     p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
216
217     val = gnutls_record_recv( p_sys->session, buf, i_length );
218     /* TODO: handle fatal error */
219     return val < 0 ? -1 : val;
220 }
221
222
223 /*****************************************************************************
224  * tls_Session(Continue)?Handshake:
225  *****************************************************************************
226  * Establishes TLS session with a peer through socket <fd>.
227  * Returns -1 on error (you need not and must not call tls_SessionClose)
228  * 0 on succesful handshake completion, 1 if more would-be blocking recv is
229  * needed, 2 if more would-be blocking send is required.
230  *****************************************************************************/
231 static int
232 gnutls_ContinueHandshake( tls_session_t *p_session)
233 {
234     tls_session_sys_t *p_sys;
235     int val;
236
237     p_sys = (tls_session_sys_t *)(p_session->p_sys);
238
239      /* TODO: handle fatal error */
240 #ifdef WIN32
241     WSASetLastError( 0 );
242 #endif
243     val = gnutls_handshake( p_sys->session );
244     if( ( val == GNUTLS_E_AGAIN ) || ( val == GNUTLS_E_INTERRUPTED ) )
245         return 1 + gnutls_record_get_direction( p_sys->session );
246
247     if( val < 0 )
248     {
249 #ifdef WIN32
250         msg_Dbg( p_session, "Winsock error %d", WSAGetLastError( ) );
251 #endif
252         msg_Err( p_session, "TLS handshake failed: %s",
253                  gnutls_strerror( val ) );
254         p_session->pf_close( p_session );
255         return -1;
256     }
257
258     p_sys->b_handshaked = VLC_TRUE;
259     return 0;
260 }
261
262 static int
263 gnutls_VerifyHostname( vlc_object_t *p_this, gnutls_session session,
264                        const char *psz_hostname )
265 {
266     const gnutls_datum *p_data;
267     gnutls_x509_crt cert;
268     unsigned status;
269     int val;
270
271     /* certificate (host)name verification */
272     p_data = gnutls_certificate_get_peers( session, &status );
273     if( p_data == NULL )
274     {
275         msg_Err( p_this, "TLS peer certificate not available" );
276         return -1;
277     }
278
279     val = gnutls_x509_crt_init( &cert );
280     if( val )
281     {
282         msg_Err( p_this, "x509 fatal error: %s", gnutls_strerror( val ) );
283         return -1;
284     }
285
286     val = gnutls_x509_crt_import( cert, p_data, GNUTLS_X509_FMT_DER );
287     if( val )
288     {
289         msg_Err( p_this, "x509 certificate import error: %s",
290                 gnutls_strerror( val ) );
291         gnutls_x509_crt_deinit( cert );
292         return -1;
293     }
294
295     if( gnutls_x509_crt_check_hostname( cert, psz_hostname ) == 0 )
296     {
297         msg_Err( p_this, "x509 certificate does not match \"%s\"",
298                 psz_hostname );
299         gnutls_x509_crt_deinit( cert );
300         return -1;
301     }
302
303     gnutls_x509_crt_deinit( cert );
304     msg_Dbg( p_this, "x509 hostname matches %s", psz_hostname );
305     return 0;
306 }
307
308 static int
309 gnutls_HandshakeAndValidate( tls_session_t *p_session )
310 {
311     int val;
312
313     val = gnutls_ContinueHandshake( p_session );
314     if( val == 0 )
315     {
316         unsigned status;
317         tls_session_sys_t *p_sys;
318
319         p_sys = (tls_session_sys_t *)(p_session->p_sys);
320         /* certificates chain verification */
321         val = gnutls_certificate_verify_peers2( p_sys->session, &status );
322
323         if( val )
324         {
325             msg_Err( p_session, "TLS certificate verification failed: %s",
326                      gnutls_strerror( val ) );
327             p_session->pf_close( p_session );
328             return -1;
329         }
330
331         if( status )
332         {
333             msg_Warn( p_session, "TLS session: access denied" );
334             if( status & GNUTLS_CERT_INVALID )
335                 msg_Dbg( p_session, "certificate could not be verified" );
336             if( status & GNUTLS_CERT_REVOKED )
337                 msg_Dbg( p_session, "certificate was revoked" );
338             if( status & GNUTLS_CERT_SIGNER_NOT_FOUND )
339                 msg_Dbg( p_session, "certificate's signer was not found" );
340             if( status & GNUTLS_CERT_SIGNER_NOT_CA )
341                 msg_Dbg( p_session, "certificate's signer is not a CA" );
342             p_session->pf_close( p_session );
343             return -1;
344         }
345
346         msg_Dbg( p_session, "TLS certificate verified" );
347         if( ( p_sys->psz_hostname != NULL )
348          && gnutls_VerifyHostname( (vlc_object_t *)p_session, p_sys->session,
349                                    p_sys->psz_hostname ) )
350         {
351             p_session->pf_close( p_session );
352             return -1;
353         }
354         return 0;
355     }
356
357     return val;
358 }
359
360 /**
361  * Starts negociation of a TLS session.
362  *
363  * @param fd stream socket already connected with the peer.
364  * @param psz_hostname if not NULL, hostname to mention as a Server Name.
365  *
366  * @return -1 on error (you need not and must not call tls_SessionClose),
367  * 0 on succesful handshake completion, 1 if more would-be blocking recv is
368  * needed, 2 if more would-be blocking send is required.
369  */
370 static int
371 gnutls_BeginHandshake( tls_session_t *p_session, int fd,
372                        const char *psz_hostname )
373 {
374     tls_session_sys_t *p_sys;
375
376     p_sys = (tls_session_sys_t *)(p_session->p_sys);
377
378     gnutls_transport_set_ptr (p_sys->session, (gnutls_transport_ptr)(intptr_t)fd);
379
380     if( psz_hostname != NULL )
381     {
382         gnutls_server_name_set( p_sys->session, GNUTLS_NAME_DNS, psz_hostname,
383                                 strlen( psz_hostname ) );
384         if( get_Bool( p_session, "tls-check-hostname" ) )
385         {
386             p_sys->psz_hostname = strdup( psz_hostname );
387             if( p_sys->psz_hostname == NULL )
388             {
389                 p_session->pf_close( p_session );
390                 return -1;
391             }
392         }
393     }
394
395     return p_session->pf_handshake2( p_session );
396 }
397
398 /**
399  * Terminates TLS session and releases session data.
400  * You still have to close the socket yourself.
401  */
402 static void
403 gnutls_SessionClose( tls_session_t *p_session )
404 {
405     tls_session_sys_t *p_sys;
406
407     p_sys = (tls_session_sys_t *)(p_session->p_sys);
408
409     if( p_sys->b_handshaked == VLC_TRUE )
410         gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
411     gnutls_deinit( p_sys->session );
412
413     if( p_sys->psz_hostname != NULL )
414         free( p_sys->psz_hostname );
415
416     vlc_object_detach( p_session );
417     vlc_object_destroy( p_session );
418
419     free( p_sys );
420 }
421
422 static void
423 gnutls_ClientDelete( tls_session_t *p_session )
424 {
425     /* On the client-side, credentials are re-allocated per session */
426     gnutls_certificate_credentials x509_cred =
427                         ((tls_client_sys_t *)(p_session->p_sys))->x509_cred;
428
429     gnutls_SessionClose( p_session );
430
431     /* credentials must be free'd *after* gnutls_deinit() */
432     gnutls_certificate_free_credentials( x509_cred );
433 }
434
435
436 static int
437 gnutls_Addx509File( vlc_object_t *p_this,
438                     gnutls_certificate_credentials cred,
439                     const char *psz_path, vlc_bool_t b_priv );
440
441 static int
442 gnutls_Addx509Directory( vlc_object_t *p_this,
443                          gnutls_certificate_credentials cred,
444                          const char *psz_dirname,
445                          vlc_bool_t b_priv )
446 {
447     DIR* dir;
448     const char *psz_dirent;
449
450     if( *psz_dirname == '\0' )
451         psz_dirname = ".";
452
453     dir = utf8_opendir( psz_dirname );
454     if( dir == NULL )
455     {
456         msg_Warn( p_this, "cannot open directory (%s): %s", psz_dirname,
457                   strerror( errno ) );
458         return VLC_EGENERIC;
459     }
460 #ifdef S_ISLNK
461     else
462     {
463         struct stat st1, st2;
464         int fd = dirfd( dir );
465
466         /*
467          * Gets stats for the directory path, checks that it is not a
468          * symbolic link (to avoid possibly infinite recursion), and verifies
469          * that the inode is still the same, to avoid TOCTOU race condition.
470          */
471         if( ( fd == -1)
472          || fstat( fd, &st1 ) || utf8_lstat( psz_dirname, &st2 )
473          || S_ISLNK( st2.st_mode ) || ( st1.st_ino != st2.st_ino ) )
474         {
475             closedir( dir );
476             return VLC_EGENERIC;
477         }
478     }
479 #endif
480
481     while( ( psz_dirent = utf8_readdir( dir ) ) != NULL )
482     {
483         char *psz_filename;
484         int check;
485
486         if( ( strcmp( ".", psz_dirent ) == 0 )
487          || ( strcmp( "..", psz_dirent ) == 0 ) )
488             continue;
489
490         check = asprintf( &psz_filename, "%s/%s", psz_dirname,
491                               psz_dirent );
492         LocaleFree( psz_dirent );
493         if( check == -1 )
494             continue;
495
496         gnutls_Addx509File( p_this, cred, psz_filename, b_priv );
497         free( psz_filename );
498     }
499
500     closedir( dir );
501     return VLC_SUCCESS;
502 }
503
504
505 static int
506 gnutls_Addx509File( vlc_object_t *p_this,
507                     gnutls_certificate_credentials cred,
508                     const char *psz_path, vlc_bool_t b_priv )
509 {
510     struct stat st;
511
512     if( utf8_stat( psz_path, &st ) == 0 )
513     {
514         if( S_ISREG( st.st_mode ) )
515         {
516             char *psz_localname = ToLocale( psz_path );
517             int i = b_priv
518                     ? gnutls_certificate_set_x509_key_file( cred,
519                     psz_localname,  psz_localname, GNUTLS_X509_FMT_PEM )
520                 : gnutls_certificate_set_x509_trust_file( cred,
521                         psz_localname, GNUTLS_X509_FMT_PEM );
522             LocaleFree( psz_localname );
523
524             if( i < 0 )
525             {
526                 msg_Warn( p_this, "cannot add x509 credentials (%s): %s",
527                           psz_path, gnutls_strerror( i ) );
528                 return VLC_EGENERIC;
529             }
530             else
531             {
532                 msg_Dbg( p_this, "added x509 credentials (%s)",
533                          psz_path );
534                 return VLC_SUCCESS;
535             }
536         }
537         else if( S_ISDIR( st.st_mode ) )
538         {
539             msg_Dbg( p_this,
540                      "looking recursively for x509 credentials in %s",
541                      psz_path );
542             return gnutls_Addx509Directory( p_this, cred, psz_path, b_priv);
543         }
544     }
545     else
546         msg_Warn( p_this, "cannot add x509 credentials (%s): %s",
547                   psz_path, strerror( errno ) );
548     return VLC_EGENERIC;
549 }
550
551
552 /**
553  * Initializes a client-side TLS session.
554  */
555 static tls_session_t *
556 gnutls_ClientCreate( tls_t *p_tls )
557 {
558     tls_session_t *p_session = NULL;
559     tls_client_sys_t *p_sys = NULL;
560     int i_val;
561     const int cert_type_priority[3] =
562     {
563         GNUTLS_CRT_X509,
564         0
565     };
566
567     p_sys = (tls_client_sys_t *)malloc( sizeof(struct tls_client_sys_t) );
568     if( p_sys == NULL )
569         return NULL;
570
571     p_session = (struct tls_session_t *)vlc_object_create ( p_tls, sizeof(struct tls_session_t) );
572     if( p_session == NULL )
573     {
574         free( p_sys );
575         return NULL;
576     }
577
578     p_session->p_sys = p_sys;
579     p_session->sock.p_sys = p_session;
580     p_session->sock.pf_send = gnutls_Send;
581     p_session->sock.pf_recv = gnutls_Recv;
582     p_session->pf_handshake = gnutls_BeginHandshake;
583     p_session->pf_close = gnutls_ClientDelete;
584
585     p_sys->session.b_handshaked = VLC_FALSE;
586     p_sys->session.psz_hostname = NULL;
587
588     vlc_object_attach( p_session, p_tls );
589
590     i_val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
591     if( i_val != 0 )
592     {
593         msg_Err( p_tls, "cannot allocate X509 credentials: %s",
594                  gnutls_strerror( i_val ) );
595         goto error;
596     }
597
598     if( get_Bool( p_tls, "tls-check-cert" ) )
599     {
600         char *psz_path;
601
602         if( asprintf( &psz_path, "%s/"CONFIG_DIR"/ssl/certs",
603                       p_tls->p_vlc->psz_homedir ) != -1 )
604         {
605             gnutls_Addx509Directory( (vlc_object_t *)p_session,
606                                      p_sys->x509_cred, psz_path, VLC_FALSE );
607             free( psz_path );
608         }
609
610         if( asprintf( &psz_path, "%s/ca-certificates.crt",
611             config_GetDataDir ( (vlc_object_t *)p_session) ) != -1 )
612         {
613             gnutls_Addx509File( (vlc_object_t *)p_session,
614                                 p_sys->x509_cred, psz_path, VLC_FALSE );
615             free( psz_path );
616         }
617         p_session->pf_handshake2 = gnutls_HandshakeAndValidate;
618     }
619     else
620         p_session->pf_handshake2 = gnutls_ContinueHandshake;
621
622     {
623         char *psz_path;
624
625         if( asprintf( &psz_path, "%s/"CONFIG_DIR"/ssl/private",
626                       p_tls->p_vlc->psz_homedir ) == -1 )
627         {
628             gnutls_certificate_free_credentials( p_sys->x509_cred );
629             goto error;
630         }
631
632         gnutls_Addx509Directory( (vlc_object_t *)p_session, p_sys->x509_cred,
633                                  psz_path, VLC_TRUE );
634
635         free( psz_path );
636     }
637
638     i_val = gnutls_init( &p_sys->session.session, GNUTLS_CLIENT );
639     if( i_val != 0 )
640     {
641         msg_Err( p_tls, "cannot initialize TLS session: %s",
642                  gnutls_strerror( i_val ) );
643         gnutls_certificate_free_credentials( p_sys->x509_cred );
644         goto error;
645     }
646
647     i_val = gnutls_set_default_priority( p_sys->session.session );
648     if( i_val < 0 )
649     {
650         msg_Err( p_tls, "cannot set ciphers priorities: %s",
651                  gnutls_strerror( i_val ) );
652         gnutls_deinit( p_sys->session.session );
653         gnutls_certificate_free_credentials( p_sys->x509_cred );
654         goto error;
655     }
656
657     i_val = gnutls_certificate_type_set_priority( p_sys->session.session,
658                                                   cert_type_priority );
659     if( i_val < 0 )
660     {
661         msg_Err( p_tls, "cannot set certificate type priorities: %s",
662                  gnutls_strerror( i_val ) );
663         gnutls_deinit( p_sys->session.session );
664         gnutls_certificate_free_credentials( p_sys->x509_cred );
665         goto error;
666     }
667
668     i_val = gnutls_credentials_set( p_sys->session.session,
669                                     GNUTLS_CRD_CERTIFICATE,
670                                     p_sys->x509_cred );
671     if( i_val < 0 )
672     {
673         msg_Err( p_tls, "cannot set TLS session credentials: %s",
674                  gnutls_strerror( i_val ) );
675         gnutls_deinit( p_sys->session.session );
676         gnutls_certificate_free_credentials( p_sys->x509_cred );
677         goto error;
678     }
679
680     return p_session;
681
682 error:
683     vlc_object_detach( p_session );
684     vlc_object_destroy( p_session );
685     free( p_sys );
686
687     return NULL;
688 }
689
690
691 /**
692  * TLS session resumption callbacks (server-side)
693  */
694 static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
695 {
696     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
697
698     if( ( p_sys->i_cache_size == 0 )
699      || ( key.size > MAX_SESSION_ID )
700      || ( data.size > MAX_SESSION_DATA ) )
701         return -1;
702
703     vlc_mutex_lock( &p_sys->cache_lock );
704
705     memcpy( p_sys->p_store->id, key.data, key.size);
706     memcpy( p_sys->p_store->data, data.data, data.size );
707     p_sys->p_store->i_idlen = key.size;
708     p_sys->p_store->i_datalen = data.size;
709
710     p_sys->p_store++;
711     if( ( p_sys->p_store - p_sys->p_cache ) == p_sys->i_cache_size )
712         p_sys->p_store = p_sys->p_cache;
713
714     vlc_mutex_unlock( &p_sys->cache_lock );
715
716     return 0;
717 }
718
719
720 static const gnutls_datum err_datum = { NULL, 0 };
721
722 static gnutls_datum cb_fetch( void *p_server, gnutls_datum key )
723 {
724     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
725     saved_session_t *p_session, *p_end;
726
727     p_session = p_sys->p_cache;
728     p_end = p_session + p_sys->i_cache_size;
729
730     vlc_mutex_lock( &p_sys->cache_lock );
731
732     while( p_session < p_end )
733     {
734         if( ( p_session->i_idlen == key.size )
735          && !memcmp( p_session->id, key.data, key.size ) )
736         {
737             gnutls_datum data;
738
739             data.size = p_session->i_datalen;
740
741             data.data = gnutls_malloc( data.size );
742             if( data.data == NULL )
743             {
744                 vlc_mutex_unlock( &p_sys->cache_lock );
745                 return err_datum;
746             }
747
748             memcpy( data.data, p_session->data, data.size );
749             vlc_mutex_unlock( &p_sys->cache_lock );
750             return data;
751         }
752         p_session++;
753     }
754
755     vlc_mutex_unlock( &p_sys->cache_lock );
756
757     return err_datum;
758 }
759
760
761 static int cb_delete( void *p_server, gnutls_datum key )
762 {
763     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
764     saved_session_t *p_session, *p_end;
765
766     p_session = p_sys->p_cache;
767     p_end = p_session + p_sys->i_cache_size;
768
769     vlc_mutex_lock( &p_sys->cache_lock );
770
771     while( p_session < p_end )
772     {
773         if( ( p_session->i_idlen == key.size )
774          && !memcmp( p_session->id, key.data, key.size ) )
775         {
776             p_session->i_datalen = p_session->i_idlen = 0;
777             vlc_mutex_unlock( &p_sys->cache_lock );
778             return 0;
779         }
780         p_session++;
781     }
782
783     vlc_mutex_unlock( &p_sys->cache_lock );
784
785     return -1;
786 }
787
788
789 /**
790  * Initializes a server-side TLS session.
791  */
792 static tls_session_t *
793 gnutls_ServerSessionPrepare( tls_server_t *p_server )
794 {
795     tls_session_t *p_session;
796     tls_server_sys_t *p_server_sys;
797     gnutls_session session;
798     int i_val;
799
800     p_session = vlc_object_create( p_server, sizeof (struct tls_session_t) );
801     if( p_session == NULL )
802         return NULL;
803
804     p_session->p_sys = malloc( sizeof(struct tls_session_sys_t) );
805     if( p_session->p_sys == NULL )
806     {
807         vlc_object_destroy( p_session );
808         return NULL;
809     }
810
811     vlc_object_attach( p_session, p_server );
812
813     p_server_sys = (tls_server_sys_t *)p_server->p_sys;
814     p_session->sock.p_sys = p_session;
815     p_session->sock.pf_send = gnutls_Send;
816     p_session->sock.pf_recv = gnutls_Recv;
817     p_session->pf_handshake = gnutls_BeginHandshake;
818     p_session->pf_handshake2 = p_server_sys->pf_handshake2;
819     p_session->pf_close = gnutls_SessionClose;
820
821     ((tls_session_sys_t *)p_session->p_sys)->b_handshaked = VLC_FALSE;
822     ((tls_session_sys_t *)p_session->p_sys)->psz_hostname = NULL;
823
824     i_val = gnutls_init( &session, GNUTLS_SERVER );
825     if( i_val != 0 )
826     {
827         msg_Err( p_server, "cannot initialize TLS session: %s",
828                  gnutls_strerror( i_val ) );
829         goto error;
830     }
831
832     ((tls_session_sys_t *)p_session->p_sys)->session = session;
833
834     i_val = gnutls_set_default_priority( session );
835     if( i_val < 0 )
836     {
837         msg_Err( p_server, "cannot set ciphers priorities: %s",
838                  gnutls_strerror( i_val ) );
839         gnutls_deinit( session );
840         goto error;
841     }
842
843     i_val = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE,
844                                     p_server_sys->x509_cred );
845     if( i_val < 0 )
846     {
847         msg_Err( p_server, "cannot set TLS session credentials: %s",
848                  gnutls_strerror( i_val ) );
849         gnutls_deinit( session );
850         goto error;
851     }
852
853     if( p_session->pf_handshake2 == gnutls_HandshakeAndValidate )
854         gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUIRE );
855
856     gnutls_dh_set_prime_bits( session, get_Int( p_server, "gnutls-dh-bits" ) );
857
858     /* Session resumption support */
859     gnutls_db_set_cache_expiration( session, get_Int( p_server,
860                                     "gnutls-cache-expiration" ) );
861     gnutls_db_set_retrieve_function( session, cb_fetch );
862     gnutls_db_set_remove_function( session, cb_delete );
863     gnutls_db_set_store_function( session, cb_store );
864     gnutls_db_set_ptr( session, p_server );
865
866     return p_session;
867
868 error:
869     free( p_session->p_sys );
870     vlc_object_detach( p_session );
871     vlc_object_destroy( p_session );
872     return NULL;
873 }
874
875
876 /**
877  * Releases data allocated with tls_ServerCreate().
878  */
879 static void
880 gnutls_ServerDelete( tls_server_t *p_server )
881 {
882     tls_server_sys_t *p_sys;
883     p_sys = (tls_server_sys_t *)p_server->p_sys;
884
885     vlc_mutex_destroy( &p_sys->cache_lock );
886     free( p_sys->p_cache );
887
888     vlc_object_detach( p_server );
889     vlc_object_destroy( p_server );
890
891     /* all sessions depending on the server are now deinitialized */
892     gnutls_certificate_free_credentials( p_sys->x509_cred );
893     gnutls_dh_params_deinit( p_sys->dh_params );
894     free( p_sys );
895 }
896
897
898 /**
899  * Adds one or more certificate authorities.
900  *
901  * @param psz_ca_path (Unicode) path to an x509 certificates list.
902  *
903  * @return -1 on error, 0 on success.
904  *****************************************************************************/
905 static int
906 gnutls_ServerAddCA( tls_server_t *p_server, const char *psz_ca_path )
907 {
908     tls_server_sys_t *p_sys;
909     char *psz_local_path;
910     int val;
911
912     p_sys = (tls_server_sys_t *)(p_server->p_sys);
913
914     psz_local_path = ToLocale( psz_ca_path );
915     val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred,
916                                                   psz_local_path,
917                                                   GNUTLS_X509_FMT_PEM );
918     LocaleFree( psz_local_path );
919     if( val < 0 )
920     {
921         msg_Err( p_server, "cannot add trusted CA (%s): %s", psz_ca_path,
922                  gnutls_strerror( val ) );
923         return VLC_EGENERIC;
924     }
925     msg_Dbg( p_server, " %d trusted CA added (%s)", val, psz_ca_path );
926
927     /* enables peer's certificate verification */
928     p_sys->pf_handshake2 = gnutls_HandshakeAndValidate;
929
930     return VLC_SUCCESS;
931 }
932
933
934 /**
935  * Adds a certificates revocation list to be sent to TLS clients.
936  *
937  * @param psz_crl_path (Unicode) path of the CRL file.
938  *
939  * @return -1 on error, 0 on success.
940  */
941 static int
942 gnutls_ServerAddCRL( tls_server_t *p_server, const char *psz_crl_path )
943 {
944     int val;
945     char *psz_local_path = ToLocale( psz_crl_path );
946
947     val = gnutls_certificate_set_x509_crl_file( ((tls_server_sys_t *)
948                                                 (p_server->p_sys))->x509_cred,
949                                                 psz_local_path,
950                                                 GNUTLS_X509_FMT_PEM );
951     LocaleFree( psz_crl_path );
952     if( val < 0 )
953     {
954         msg_Err( p_server, "cannot add CRL (%s): %s", psz_crl_path,
955                  gnutls_strerror( val ) );
956         return VLC_EGENERIC;
957     }
958     msg_Dbg( p_server, "%d CRL added (%s)", val, psz_crl_path );
959     return VLC_SUCCESS;
960 }
961
962
963 /**
964  * Allocates a whole server's TLS credentials.
965  *
966  * @return NULL on error.
967  */
968 static tls_server_t *
969 gnutls_ServerCreate( tls_t *p_tls, const char *psz_cert_path,
970                      const char *psz_key_path )
971 {
972     tls_server_t *p_server;
973     tls_server_sys_t *p_sys;
974     char *psz_local_key, *psz_local_cert;
975     int val;
976
977     msg_Dbg( p_tls, "creating TLS server" );
978
979     p_sys = (tls_server_sys_t *)malloc( sizeof(struct tls_server_sys_t) );
980     if( p_sys == NULL )
981         return NULL;
982
983     p_sys->i_cache_size = get_Int( p_tls, "gnutls-cache-size" );
984     p_sys->p_cache = (struct saved_session_t *)calloc( p_sys->i_cache_size,
985                                            sizeof( struct saved_session_t ) );
986     if( p_sys->p_cache == NULL )
987     {
988         free( p_sys );
989         return NULL;
990     }
991     p_sys->p_store = p_sys->p_cache;
992
993     p_server = vlc_object_create( p_tls, sizeof(struct tls_server_t) );
994     if( p_server == NULL )
995     {
996         free( p_sys->p_cache );
997         free( p_sys );
998         return NULL;
999     }
1000
1001     vlc_object_attach( p_server, p_tls );
1002
1003     p_server->p_sys = p_sys;
1004     p_server->pf_delete = gnutls_ServerDelete;
1005     p_server->pf_add_CA = gnutls_ServerAddCA;
1006     p_server->pf_add_CRL = gnutls_ServerAddCRL;
1007     p_server->pf_session_prepare = gnutls_ServerSessionPrepare;
1008
1009     /* No certificate validation by default */
1010     p_sys->pf_handshake2 = gnutls_ContinueHandshake;
1011
1012     /* FIXME: check for errors */
1013     vlc_mutex_init( p_server, &p_sys->cache_lock );
1014
1015     /* Sets server's credentials */
1016     val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
1017     if( val != 0 )
1018     {
1019         msg_Err( p_server, "cannot allocate X509 credentials: %s",
1020                  gnutls_strerror( val ) );
1021         goto error;
1022     }
1023
1024     psz_local_cert = ToLocale( psz_cert_path );
1025     psz_local_key = ToLocale( psz_key_path );
1026     val = gnutls_certificate_set_x509_key_file( p_sys->x509_cred,
1027                                                 psz_local_cert, psz_local_key,
1028                                                 GNUTLS_X509_FMT_PEM );
1029     LocaleFree( psz_cert_path );
1030     LocaleFree( psz_key_path );
1031     if( val < 0 )
1032     {
1033         msg_Err( p_server, "cannot set certificate chain or private key: %s",
1034                  gnutls_strerror( val ) );
1035         gnutls_certificate_free_credentials( p_sys->x509_cred );
1036         goto error;
1037     }
1038
1039     /* FIXME:
1040      * - regenerate these regularly
1041      * - support other ciper suites
1042      */
1043     val = gnutls_dh_params_init( &p_sys->dh_params );
1044     if( val >= 0 )
1045     {
1046         msg_Dbg( p_server, "computing Diffie Hellman ciphers parameters" );
1047         val = gnutls_dh_params_generate2( p_sys->dh_params,
1048                                           get_Int( p_tls, "gnutls-dh-bits" ) );
1049     }
1050     if( val < 0 )
1051     {
1052         msg_Err( p_server, "cannot initialize DH cipher suites: %s",
1053                  gnutls_strerror( val ) );
1054         gnutls_certificate_free_credentials( p_sys->x509_cred );
1055         goto error;
1056     }
1057     msg_Dbg( p_server, "ciphers parameters computed" );
1058
1059     gnutls_certificate_set_dh_params( p_sys->x509_cred, p_sys->dh_params);
1060
1061     return p_server;
1062
1063 error:
1064     vlc_mutex_destroy( &p_sys->cache_lock );
1065     vlc_object_detach( p_server );
1066     vlc_object_destroy( p_server );
1067     free( p_sys );
1068     return NULL;
1069 }
1070
1071
1072 /**
1073  * gcrypt thread option VLC implementation
1074  */
1075 vlc_object_t *__p_gcry_data;
1076
1077 static int gcry_vlc_mutex_init( void **p_sys )
1078 {
1079     int i_val;
1080     vlc_mutex_t *p_lock = (vlc_mutex_t *)malloc( sizeof( vlc_mutex_t ) );
1081
1082     if( p_lock == NULL)
1083         return ENOMEM;
1084
1085     i_val = vlc_mutex_init( __p_gcry_data, p_lock );
1086     if( i_val )
1087         free( p_lock );
1088     else
1089         *p_sys = p_lock;
1090     return i_val;
1091 }
1092
1093 static int gcry_vlc_mutex_destroy( void **p_sys )
1094 {
1095     int i_val;
1096     vlc_mutex_t *p_lock = (vlc_mutex_t *)*p_sys;
1097
1098     i_val = vlc_mutex_destroy( p_lock );
1099     free( p_lock );
1100     return i_val;
1101 }
1102
1103 static int gcry_vlc_mutex_lock( void **p_sys )
1104 {
1105     return vlc_mutex_lock( (vlc_mutex_t *)*p_sys );
1106 }
1107
1108 static int gcry_vlc_mutex_unlock( void **lock )
1109 {
1110     return vlc_mutex_unlock( (vlc_mutex_t *)*lock );
1111 }
1112
1113 static struct gcry_thread_cbs gcry_threads_vlc =
1114 {
1115     GCRY_THREAD_OPTION_USER,
1116     NULL,
1117     gcry_vlc_mutex_init,
1118     gcry_vlc_mutex_destroy,
1119     gcry_vlc_mutex_lock,
1120     gcry_vlc_mutex_unlock
1121 };
1122
1123
1124 /*****************************************************************************
1125  * Module initialization
1126  *****************************************************************************/
1127 static int
1128 Open( vlc_object_t *p_this )
1129 {
1130     tls_t *p_tls = (tls_t *)p_this;
1131
1132     vlc_value_t lock, count;
1133
1134     var_Create( p_this->p_libvlc, "gnutls_mutex", VLC_VAR_MUTEX );
1135     var_Get( p_this->p_libvlc, "gnutls_mutex", &lock );
1136     vlc_mutex_lock( lock.p_address );
1137
1138     /* Initialize GnuTLS only once */
1139     var_Create( p_this->p_libvlc, "gnutls_count", VLC_VAR_INTEGER );
1140     var_Get( p_this->p_libvlc, "gnutls_count", &count);
1141
1142     if( count.i_int == 0)
1143     {
1144         const char *psz_version;
1145
1146         __p_gcry_data = VLC_OBJECT( p_this->p_vlc );
1147
1148         gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_vlc);
1149         if( gnutls_global_init( ) )
1150         {
1151             msg_Warn( p_this, "cannot initialize GnuTLS" );
1152             vlc_mutex_unlock( lock.p_address );
1153             return VLC_EGENERIC;
1154         }
1155         /*
1156          * FIXME: in fact, we currently depends on 1.0.17, but it breaks on
1157          * Debian which as a patched 1.0.16 (which we can use).
1158          */
1159         psz_version = gnutls_check_version( "1.0.16" );
1160         if( psz_version == NULL )
1161         {
1162             gnutls_global_deinit( );
1163             vlc_mutex_unlock( lock.p_address );
1164             msg_Err( p_this, "unsupported GnuTLS version" );
1165             return VLC_EGENERIC;
1166         }
1167         msg_Dbg( p_this, "GnuTLS v%s initialized", psz_version );
1168     }
1169
1170     count.i_int++;
1171     var_Set( p_this->p_libvlc, "gnutls_count", count);
1172     vlc_mutex_unlock( lock.p_address );
1173
1174     p_tls->pf_server_create = gnutls_ServerCreate;
1175     p_tls->pf_client_create = gnutls_ClientCreate;
1176     return VLC_SUCCESS;
1177 }
1178
1179
1180 /*****************************************************************************
1181  * Module deinitialization
1182  *****************************************************************************/
1183 static void
1184 Close( vlc_object_t *p_this )
1185 {
1186     /*tls_t *p_tls = (tls_t *)p_this;
1187     tls_sys_t *p_sys = (tls_sys_t *)(p_this->p_sys);*/
1188
1189     vlc_value_t lock, count;
1190
1191     var_Create( p_this->p_libvlc, "gnutls_mutex", VLC_VAR_MUTEX );
1192     var_Get( p_this->p_libvlc, "gnutls_mutex", &lock );
1193     vlc_mutex_lock( lock.p_address );
1194
1195     var_Create( p_this->p_libvlc, "gnutls_count", VLC_VAR_INTEGER );
1196     var_Get( p_this->p_libvlc, "gnutls_count", &count);
1197     count.i_int--;
1198     var_Set( p_this->p_libvlc, "gnutls_count", count);
1199
1200     if( count.i_int == 0 )
1201     {
1202         gnutls_global_deinit( );
1203         msg_Dbg( p_this, "GnuTLS deinitialized" );
1204     }
1205
1206     vlc_mutex_unlock( lock.p_address );
1207 }