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