Use static mutexes
[vlc.git] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * gnutls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2006 Rémi Denis-Courmont
5  * $Id$
6  *
7  * Authors: Rémi Denis-Courmont <rem # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #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
727     p_sys->session.psz_hostname = servername;
728     gnutls_server_name_set (p_sys->session.session, GNUTLS_NAME_DNS,
729                             servername, strlen (servername));
730
731     return VLC_SUCCESS;
732
733 s_error:
734     gnutls_deinit (p_sys->session.session);
735     gnutls_certificate_free_credentials (p_sys->x509_cred);
736 error:
737     gnutls_Deinit (obj);
738     free (p_sys);
739     return VLC_EGENERIC;
740 }
741
742
743 static void CloseClient (vlc_object_t *obj)
744 {
745     tls_session_t *client = (tls_session_t *)obj;
746     tls_client_sys_t *p_sys = (tls_client_sys_t *)(client->p_sys);
747
748     if (p_sys->session.b_handshaked == true)
749         gnutls_bye (p_sys->session.session, GNUTLS_SHUT_WR);
750     gnutls_deinit (p_sys->session.session);
751     /* credentials must be free'd *after* gnutls_deinit() */
752     gnutls_certificate_free_credentials (p_sys->x509_cred);
753
754     gnutls_Deinit (obj);
755     free (p_sys->session.psz_hostname);
756     free (p_sys);
757 }
758
759
760 /**
761  * Server-side TLS
762  */
763 struct tls_server_sys_t
764 {
765     gnutls_certificate_credentials_t x509_cred;
766     gnutls_dh_params_t               dh_params;
767
768     struct saved_session_t          *p_cache;
769     struct saved_session_t          *p_store;
770     int                              i_cache_size;
771     vlc_mutex_t                      cache_lock;
772
773     int                            (*pf_handshake) (tls_session_t *);
774 };
775
776
777 /**
778  * TLS session resumption callbacks (server-side)
779  */
780 #define MAX_SESSION_ID    32
781 #define MAX_SESSION_DATA  1024
782
783 typedef struct saved_session_t
784 {
785     char id[MAX_SESSION_ID];
786     char data[MAX_SESSION_DATA];
787
788     unsigned i_idlen;
789     unsigned i_datalen;
790 } saved_session_t;
791
792
793 static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
794 {
795     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
796
797     if( ( p_sys->i_cache_size == 0 )
798      || ( key.size > MAX_SESSION_ID )
799      || ( data.size > MAX_SESSION_DATA ) )
800         return -1;
801
802     vlc_mutex_lock( &p_sys->cache_lock );
803
804     memcpy( p_sys->p_store->id, key.data, key.size);
805     memcpy( p_sys->p_store->data, data.data, data.size );
806     p_sys->p_store->i_idlen = key.size;
807     p_sys->p_store->i_datalen = data.size;
808
809     p_sys->p_store++;
810     if( ( p_sys->p_store - p_sys->p_cache ) == p_sys->i_cache_size )
811         p_sys->p_store = p_sys->p_cache;
812
813     vlc_mutex_unlock( &p_sys->cache_lock );
814
815     return 0;
816 }
817
818
819 static gnutls_datum cb_fetch( void *p_server, gnutls_datum key )
820 {
821     static const gnutls_datum_t err_datum = { NULL, 0 };
822     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
823     saved_session_t *p_session, *p_end;
824
825     p_session = p_sys->p_cache;
826     p_end = p_session + p_sys->i_cache_size;
827
828     vlc_mutex_lock( &p_sys->cache_lock );
829
830     while( p_session < p_end )
831     {
832         if( ( p_session->i_idlen == key.size )
833          && !memcmp( p_session->id, key.data, key.size ) )
834         {
835             gnutls_datum_t data;
836
837             data.size = p_session->i_datalen;
838
839             data.data = gnutls_malloc( data.size );
840             if( data.data == NULL )
841             {
842                 vlc_mutex_unlock( &p_sys->cache_lock );
843                 return err_datum;
844             }
845
846             memcpy( data.data, p_session->data, data.size );
847             vlc_mutex_unlock( &p_sys->cache_lock );
848             return data;
849         }
850         p_session++;
851     }
852
853     vlc_mutex_unlock( &p_sys->cache_lock );
854
855     return err_datum;
856 }
857
858
859 static int cb_delete( void *p_server, gnutls_datum key )
860 {
861     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
862     saved_session_t *p_session, *p_end;
863
864     p_session = p_sys->p_cache;
865     p_end = p_session + p_sys->i_cache_size;
866
867     vlc_mutex_lock( &p_sys->cache_lock );
868
869     while( p_session < p_end )
870     {
871         if( ( p_session->i_idlen == key.size )
872          && !memcmp( p_session->id, key.data, key.size ) )
873         {
874             p_session->i_datalen = p_session->i_idlen = 0;
875             vlc_mutex_unlock( &p_sys->cache_lock );
876             return 0;
877         }
878         p_session++;
879     }
880
881     vlc_mutex_unlock( &p_sys->cache_lock );
882
883     return -1;
884 }
885
886
887 /**
888  * Terminates TLS session and releases session data.
889  * You still have to close the socket yourself.
890  */
891 static void
892 gnutls_SessionClose (tls_server_t *p_server, tls_session_t *p_session)
893 {
894     tls_session_sys_t *p_sys = p_session->p_sys;
895     (void)p_server;
896
897     if( p_sys->b_handshaked == true )
898         gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
899     gnutls_deinit( p_sys->session );
900
901     vlc_object_detach( p_session );
902     vlc_object_release( p_session );
903
904     free( p_sys );
905 }
906
907
908 /**
909  * Initializes a server-side TLS session.
910  */
911 static tls_session_t *
912 gnutls_ServerSessionPrepare( tls_server_t *p_server )
913 {
914     tls_session_t *p_session;
915     tls_server_sys_t *p_server_sys;
916     gnutls_session_t session;
917     int i_val;
918
919     p_session = vlc_object_create( p_server, sizeof (struct tls_session_t) );
920     if( p_session == NULL )
921         return NULL;
922
923     p_session->p_sys = malloc( sizeof(struct tls_session_sys_t) );
924     if( p_session->p_sys == NULL )
925     {
926         vlc_object_release( p_session );
927         return NULL;
928     }
929
930     p_server_sys = p_server->p_sys;
931     p_session->sock.p_sys = p_session;
932     p_session->sock.pf_send = gnutls_Send;
933     p_session->sock.pf_recv = gnutls_Recv;
934     p_session->pf_set_fd = gnutls_SetFD;
935     p_session->pf_handshake = p_server_sys->pf_handshake;
936
937     p_session->p_sys->b_handshaked = false;
938     p_session->p_sys->psz_hostname = NULL;
939
940     i_val = gnutls_init( &session, GNUTLS_SERVER );
941     if( i_val != 0 )
942     {
943         msg_Err( p_server, "cannot initialize TLS session: %s",
944                  gnutls_strerror( i_val ) );
945         goto error;
946     }
947
948     p_session->p_sys->session = session;
949
950     if (gnutls_SessionPrioritize (VLC_OBJECT (p_session), session))
951     {
952         gnutls_deinit( session );
953         goto error;
954     }
955
956     i_val = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE,
957                                     p_server_sys->x509_cred );
958     if( i_val < 0 )
959     {
960         msg_Err( p_server, "cannot set TLS session credentials: %s",
961                  gnutls_strerror( i_val ) );
962         gnutls_deinit( session );
963         goto error;
964     }
965
966     if (p_session->pf_handshake == gnutls_HandshakeAndValidate)
967         gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUIRE);
968
969     /* Session resumption support */
970     i_val = config_GetInt (p_server, "gnutls-cache-timeout");
971     gnutls_db_set_cache_expiration (session, i_val);
972     gnutls_db_set_retrieve_function( session, cb_fetch );
973     gnutls_db_set_remove_function( session, cb_delete );
974     gnutls_db_set_store_function( session, cb_store );
975     gnutls_db_set_ptr( session, p_server );
976
977     return p_session;
978
979 error:
980     free( p_session->p_sys );
981     vlc_object_detach( p_session );
982     vlc_object_release( p_session );
983     return NULL;
984 }
985
986
987 /**
988  * Adds one or more certificate authorities.
989  *
990  * @param psz_ca_path (Unicode) path to an x509 certificates list.
991  *
992  * @return -1 on error, 0 on success.
993  */
994 static int
995 gnutls_ServerAddCA( tls_server_t *p_server, const char *psz_ca_path )
996 {
997     tls_server_sys_t *p_sys;
998     char *psz_local_path;
999     int val;
1000
1001     p_sys = (tls_server_sys_t *)(p_server->p_sys);
1002
1003     psz_local_path = ToLocale( psz_ca_path );
1004     val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred,
1005                                                   psz_local_path,
1006                                                   GNUTLS_X509_FMT_PEM );
1007     LocaleFree( psz_local_path );
1008     if( val < 0 )
1009     {
1010         msg_Err( p_server, "cannot add trusted CA (%s): %s", psz_ca_path,
1011                  gnutls_strerror( val ) );
1012         return VLC_EGENERIC;
1013     }
1014     msg_Dbg( p_server, " %d trusted CA added (%s)", val, psz_ca_path );
1015
1016     /* enables peer's certificate verification */
1017     p_sys->pf_handshake = gnutls_HandshakeAndValidate;
1018
1019     return VLC_SUCCESS;
1020 }
1021
1022
1023 /**
1024  * Adds a certificates revocation list to be sent to TLS clients.
1025  *
1026  * @param psz_crl_path (Unicode) path of the CRL file.
1027  *
1028  * @return -1 on error, 0 on success.
1029  */
1030 static int
1031 gnutls_ServerAddCRL( tls_server_t *p_server, const char *psz_crl_path )
1032 {
1033     int val;
1034     char *psz_local_path = ToLocale( psz_crl_path );
1035
1036     val = gnutls_certificate_set_x509_crl_file( ((tls_server_sys_t *)
1037                                                 (p_server->p_sys))->x509_cred,
1038                                                 psz_local_path,
1039                                                 GNUTLS_X509_FMT_PEM );
1040     LocaleFree( psz_crl_path );
1041     if( val < 0 )
1042     {
1043         msg_Err( p_server, "cannot add CRL (%s): %s", psz_crl_path,
1044                  gnutls_strerror( val ) );
1045         return VLC_EGENERIC;
1046     }
1047     msg_Dbg( p_server, "%d CRL added (%s)", val, psz_crl_path );
1048     return VLC_SUCCESS;
1049 }
1050
1051
1052 /**
1053  * Allocates a whole server's TLS credentials.
1054  */
1055 static int OpenServer (vlc_object_t *obj)
1056 {
1057     tls_server_t *p_server = (tls_server_t *)obj;
1058     tls_server_sys_t *p_sys;
1059     int val;
1060
1061     if (gnutls_Init (obj))
1062         return VLC_EGENERIC;
1063
1064     msg_Dbg (obj, "creating TLS server");
1065
1066     p_sys = (tls_server_sys_t *)malloc( sizeof(struct tls_server_sys_t) );
1067     if( p_sys == NULL )
1068         return VLC_ENOMEM;
1069
1070     p_sys->i_cache_size = config_GetInt (obj, "gnutls-cache-size");
1071     p_sys->p_cache = calloc (p_sys->i_cache_size,
1072                              sizeof (struct saved_session_t));
1073     if (p_sys->p_cache == NULL)
1074     {
1075         free (p_sys);
1076         return VLC_ENOMEM;
1077     }
1078
1079     p_sys->p_store = p_sys->p_cache;
1080     p_server->p_sys = p_sys;
1081     p_server->pf_add_CA  = gnutls_ServerAddCA;
1082     p_server->pf_add_CRL = gnutls_ServerAddCRL;
1083     p_server->pf_open    = gnutls_ServerSessionPrepare;
1084     p_server->pf_close   = gnutls_SessionClose;
1085
1086     /* No certificate validation by default */
1087     p_sys->pf_handshake  = gnutls_ContinueHandshake;
1088
1089     vlc_mutex_init( &p_sys->cache_lock );
1090
1091     /* Sets server's credentials */
1092     val = gnutls_certificate_allocate_credentials( &p_sys->x509_cred );
1093     if( val != 0 )
1094     {
1095         msg_Err( p_server, "cannot allocate X509 credentials: %s",
1096                  gnutls_strerror( val ) );
1097         goto error;
1098     }
1099
1100     char *psz_cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
1101     char *psz_key_path = var_GetNonEmptyString (obj, "tls-x509-key");
1102     const char *psz_local_cert = ToLocale (psz_cert_path);
1103     const char *psz_local_key = ToLocale (psz_key_path);
1104     val = gnutls_certificate_set_x509_key_file (p_sys->x509_cred,
1105                                                 psz_local_cert, psz_local_key,
1106                                                 GNUTLS_X509_FMT_PEM );
1107     LocaleFree (psz_local_key);
1108     free (psz_key_path);
1109     LocaleFree (psz_local_cert);
1110     free (psz_cert_path);
1111
1112     if( val < 0 )
1113     {
1114         msg_Err( p_server, "cannot set certificate chain or private key: %s",
1115                  gnutls_strerror( val ) );
1116         gnutls_certificate_free_credentials( p_sys->x509_cred );
1117         goto error;
1118     }
1119
1120     /* FIXME:
1121      * - support other ciper suites
1122      */
1123     val = gnutls_dh_params_init (&p_sys->dh_params);
1124     if (val >= 0)
1125     {
1126         const gnutls_datum_t data = {
1127             .data = (unsigned char *)dh_params,
1128             .size = sizeof (dh_params) - 1,
1129         };
1130
1131         val = gnutls_dh_params_import_pkcs3 (p_sys->dh_params, &data,
1132                                              GNUTLS_X509_FMT_PEM);
1133         if (val == 0)
1134             gnutls_certificate_set_dh_params (p_sys->x509_cred,
1135                                               p_sys->dh_params);
1136     }
1137     if (val < 0)
1138     {
1139         msg_Err (p_server, "cannot initialize DHE cipher suites: %s",
1140                  gnutls_strerror (val));
1141     }
1142
1143     return VLC_SUCCESS;
1144
1145 error:
1146     vlc_mutex_destroy (&p_sys->cache_lock);
1147     free (p_sys->p_cache);
1148     free (p_sys);
1149     return VLC_EGENERIC;
1150 }
1151
1152 /**
1153  * Destroys a TLS server object.
1154  */
1155 static void CloseServer (vlc_object_t *p_server)
1156 {
1157     tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
1158
1159     vlc_mutex_destroy (&p_sys->cache_lock);
1160     free (p_sys->p_cache);
1161
1162     /* all sessions depending on the server are now deinitialized */
1163     gnutls_certificate_free_credentials (p_sys->x509_cred);
1164     gnutls_dh_params_deinit (p_sys->dh_params);
1165     free (p_sys);
1166
1167     gnutls_Deinit (p_server);
1168 }