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