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