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