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