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