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