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