]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
Use standard putc_unlocked() instead of fputc_unlocked()
[vlc] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * gnutls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2011 RĂ©mi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 /*****************************************************************************
22  * Preamble
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <errno.h>
32 #include <time.h>
33
34 #include <sys/types.h>
35 #include <errno.h>
36 #ifdef HAVE_SYS_STAT_H
37 # include <sys/stat.h>
38 #endif
39 #ifdef WIN32
40 # include <io.h>
41 # include <wincrypt.h>
42 #else
43 # include <unistd.h>
44 #endif
45 # include <fcntl.h>
46
47
48 #include <vlc_tls.h>
49 #include <vlc_charset.h>
50 #include <vlc_fs.h>
51 #include <vlc_block.h>
52
53 #include <gcrypt.h>
54 #include <gnutls/gnutls.h>
55 #include <gnutls/x509.h>
56
57 #include <vlc_gcrypt.h>
58 #include "dhparams.h"
59
60 #include <assert.h>
61
62 /*****************************************************************************
63  * Module descriptor
64  *****************************************************************************/
65 static int  OpenClient  (vlc_tls_t *, int, const char *);
66 static void CloseClient (vlc_tls_t *);
67 static int  OpenServer  (vlc_object_t *);
68 static void CloseServer (vlc_object_t *);
69
70 #define PRIORITIES_TEXT N_("TLS cipher priorities")
71 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
72     "hash functions and compression methods can be selected. " \
73     "Refer to GNU TLS documentation for detailed syntax.")
74 static const char *const priorities_values[] = {
75     "PERFORMANCE",
76     "NORMAL",
77     "SECURE128",
78     "SECURE256",
79     "EXPORT",
80 };
81 static const char *const priorities_text[] = {
82     N_("Performance (prioritize faster ciphers)"),
83     N_("Normal"),
84     N_("Secure 128-bits (exclude 256-bits ciphers)"),
85     N_("Secure 256-bits (prioritize 256-bits ciphers)"),
86     N_("Export (include insecure ciphers)"),
87 };
88
89 vlc_module_begin ()
90     set_shortname( "GNU TLS" )
91     set_description( N_("GNU TLS 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_submodule ()
98         set_description( N_("GNU TLS server") )
99         set_capability( "tls server", 1 )
100         set_category( CAT_ADVANCED )
101         set_subcategory( SUBCAT_ADVANCED_MISC )
102         set_callbacks( OpenServer, CloseServer )
103
104         add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
105                     PRIORITIES_LONGTEXT, false)
106             change_string_list (priorities_values, priorities_text, NULL)
107 vlc_module_end ()
108
109 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
110
111 /**
112  * Initializes GnuTLS with proper locking.
113  * @return VLC_SUCCESS on success, a VLC error code otherwise.
114  */
115 static int gnutls_Init (vlc_object_t *p_this)
116 {
117     int ret = VLC_EGENERIC;
118
119     vlc_gcrypt_init (); /* GnuTLS depends on gcrypt */
120
121     vlc_mutex_lock (&gnutls_mutex);
122     if (gnutls_global_init ())
123     {
124         msg_Err (p_this, "cannot initialize GnuTLS");
125         goto error;
126     }
127
128     const char *psz_version = gnutls_check_version ("2.0.0");
129     if (psz_version == NULL)
130     {
131         msg_Err (p_this, "unsupported GnuTLS version");
132         gnutls_global_deinit ();
133         goto error;
134     }
135
136     msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
137     ret = VLC_SUCCESS;
138
139 error:
140     vlc_mutex_unlock (&gnutls_mutex);
141     return ret;
142 }
143
144
145 /**
146  * Deinitializes GnuTLS.
147  */
148 static void gnutls_Deinit (vlc_object_t *p_this)
149 {
150     vlc_mutex_lock (&gnutls_mutex);
151
152     gnutls_global_deinit ();
153     msg_Dbg (p_this, "GnuTLS deinitialized");
154     vlc_mutex_unlock (&gnutls_mutex);
155 }
156
157
158 static int gnutls_Error (vlc_object_t *obj, int val)
159 {
160     switch (val)
161     {
162         case GNUTLS_E_AGAIN:
163 #ifdef WIN32
164             WSASetLastError (WSAEWOULDBLOCK);
165 #else
166             errno = EAGAIN;
167 #endif
168             break;
169
170         case GNUTLS_E_INTERRUPTED:
171 #ifdef WIN32
172             WSASetLastError (WSAEINTR);
173 #else
174             errno = EINTR;
175 #endif
176             break;
177
178         default:
179             msg_Err (obj, "%s", gnutls_strerror (val));
180 #ifndef NDEBUG
181             if (!gnutls_error_is_fatal (val))
182                 msg_Err (obj, "Error above should be handled");
183 #endif
184 #ifdef WIN32
185             WSASetLastError (WSAECONNRESET);
186 #else
187             errno = ECONNRESET;
188 #endif
189     }
190     return -1;
191 }
192 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
193
194
195 struct vlc_tls_sys
196 {
197     gnutls_session_t session;
198     gnutls_certificate_credentials_t x509_cred;
199     char *hostname;
200     bool handshaked;
201 };
202
203
204 /**
205  * Sends data through a TLS session.
206  */
207 static int gnutls_Send (void *opaque, const void *buf, size_t length)
208 {
209     vlc_tls_t *session = opaque;
210     vlc_tls_sys_t *sys = session->sys;
211
212     int val = gnutls_record_send (sys->session, buf, length);
213     return (val < 0) ? gnutls_Error (session, val) : val;
214 }
215
216
217 /**
218  * Receives data through a TLS session.
219  */
220 static int gnutls_Recv (void *opaque, void *buf, size_t length)
221 {
222     vlc_tls_t *session = opaque;
223     vlc_tls_sys_t *sys = session->sys;
224
225     int val = gnutls_record_recv (sys->session, buf, length);
226     return (val < 0) ? gnutls_Error (session, val) : val;
227 }
228
229
230 /**
231  * Starts or continues the TLS handshake.
232  *
233  * @return -1 on fatal error, 0 on successful handshake completion,
234  * 1 if more would-be blocking recv is needed,
235  * 2 if more would-be blocking send is required.
236  */
237 static int gnutls_ContinueHandshake (vlc_tls_t *session)
238 {
239     vlc_tls_sys_t *sys = session->sys;
240     int val;
241
242 #ifdef WIN32
243     WSASetLastError (0);
244 #endif
245     val = gnutls_handshake (sys->session);
246     if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
247         return 1 + gnutls_record_get_direction (sys->session);
248
249     if (val < 0)
250     {
251 #ifdef WIN32
252         msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
253 #endif
254         msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
255         return -1;
256     }
257
258     sys->handshaked = true;
259     return 0;
260 }
261
262
263 typedef struct
264 {
265     int flag;
266     const char *msg;
267 } error_msg_t;
268
269 static const error_msg_t cert_errors[] =
270 {
271     { GNUTLS_CERT_INVALID,
272         "Certificate could not be verified" },
273     { GNUTLS_CERT_REVOKED,
274         "Certificate was revoked" },
275     { GNUTLS_CERT_SIGNER_NOT_FOUND,
276         "Certificate's signer was not found" },
277     { GNUTLS_CERT_SIGNER_NOT_CA,
278         "Certificate's signer is not a CA" },
279     { GNUTLS_CERT_INSECURE_ALGORITHM,
280         "Insecure certificate signature algorithm" },
281     { 0, NULL }
282 };
283
284
285 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
286 {
287     vlc_tls_sys_t *sys = session->sys;
288
289     int val = gnutls_ContinueHandshake (session);
290     if (val)
291         return val;
292
293     /* certificates chain verification */
294     unsigned status;
295
296     val = gnutls_certificate_verify_peers2 (sys->session, &status);
297     if (val)
298     {
299         msg_Err (session, "Certificate verification failed: %s",
300                  gnutls_strerror (val));
301         return -1;
302     }
303
304     if (status)
305     {
306         msg_Err (session, "TLS session: access denied");
307         for (const error_msg_t *e = cert_errors; e->flag; e++)
308         {
309             if (status & e->flag)
310             {
311                 msg_Err (session, "%s", e->msg);
312                 status &= ~e->flag;
313             }
314         }
315
316         if (status)
317             msg_Err (session,
318                      "unknown certificate error (you found a bug in VLC)");
319         return -1;
320     }
321
322     /* certificate (host)name verification */
323     const gnutls_datum_t *data;
324     data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
325     if (data == NULL)
326     {
327         msg_Err (session, "Peer certificate not available");
328         return -1;
329     }
330
331     gnutls_x509_crt_t cert;
332     val = gnutls_x509_crt_init (&cert);
333     if (val)
334     {
335         msg_Err (session, "x509 fatal error: %s", gnutls_strerror (val));
336         return -1;
337     }
338
339     val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
340     if (val)
341     {
342         msg_Err (session, "Certificate import error: %s",
343                  gnutls_strerror (val));
344         goto error;
345     }
346
347     if (sys->hostname != NULL
348      && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
349     {
350         msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
351         goto error;
352     }
353
354     time_t now;
355     time (&now);
356
357     if (gnutls_x509_crt_get_expiration_time (cert) < now)
358     {
359         msg_Err (session, "Certificate expired");
360         goto error;
361     }
362
363     if (gnutls_x509_crt_get_activation_time (cert) > now)
364     {
365         msg_Err( session, "Certificate not yet valid" );
366         goto error;
367     }
368
369     gnutls_x509_crt_deinit (cert);
370     msg_Dbg (session, "TLS/x509 certificate verified");
371     return 0;
372
373 error:
374     gnutls_x509_crt_deinit (cert);
375     return -1;
376 }
377
378 static int
379 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
380 {
381     char *priorities = var_InheritString (obj, "gnutls-priorities");
382     if (unlikely(priorities == NULL))
383         return VLC_ENOMEM;
384
385     const char *errp;
386     int val = gnutls_priority_set_direct (session, priorities, &errp);
387     if (val < 0)
388     {
389         msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
390                  gnutls_strerror (val));
391         val = VLC_EGENERIC;
392     }
393     else
394         val = VLC_SUCCESS;
395     free (priorities);
396     return val;
397 }
398
399
400 static int
401 gnutls_Addx509File( vlc_object_t *p_this,
402                     gnutls_certificate_credentials_t cred,
403                     const char *psz_path, bool b_priv );
404 #ifdef WIN32
405 static int gnutls_loadOSCAList(vlc_object_t *p_this,
406                                gnutls_certificate_credentials_t cred);
407 #endif
408
409 static int
410 gnutls_Addx509Directory( vlc_object_t *p_this,
411                          gnutls_certificate_credentials_t cred,
412                          const char *psz_dirname,
413                          bool b_priv )
414 {
415     DIR* dir;
416
417     if( *psz_dirname == '\0' )
418         psz_dirname = ".";
419
420     dir = vlc_opendir( psz_dirname );
421     if( dir == NULL )
422     {
423         if (errno != ENOENT)
424         {
425             msg_Err (p_this, "cannot open directory (%s): %m", psz_dirname);
426             return VLC_EGENERIC;
427         }
428
429         msg_Dbg (p_this, "creating empty certificate directory: %s",
430                  psz_dirname);
431         vlc_mkdir (psz_dirname, b_priv ? 0700 : 0755);
432         return VLC_SUCCESS;
433     }
434 #ifdef S_ISLNK
435     else
436     {
437         struct stat st1, st2;
438         int fd = dirfd( dir );
439
440         /*
441          * Gets stats for the directory path, checks that it is not a
442          * symbolic link (to avoid possibly infinite recursion), and verifies
443          * that the inode is still the same, to avoid TOCTOU race condition.
444          */
445         if( ( fd == -1)
446          || fstat( fd, &st1 ) || vlc_lstat( psz_dirname, &st2 )
447          || S_ISLNK( st2.st_mode ) || ( st1.st_ino != st2.st_ino ) )
448         {
449             closedir( dir );
450             return VLC_EGENERIC;
451         }
452     }
453 #endif
454
455     for (;;)
456     {
457         char *ent = vlc_readdir (dir);
458         if (ent == NULL)
459             break;
460
461         if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
462         {
463             free( ent );
464             continue;
465         }
466
467         char path[strlen (psz_dirname) + strlen (ent) + 2];
468         sprintf (path, "%s"DIR_SEP"%s", psz_dirname, ent);
469         free (ent);
470
471         gnutls_Addx509File( p_this, cred, path, b_priv );
472     }
473
474     closedir( dir );
475     return VLC_SUCCESS;
476 }
477
478
479 static int
480 gnutls_Addx509File( vlc_object_t *p_this,
481                     gnutls_certificate_credentials cred,
482                     const char *psz_path, bool b_priv )
483 {
484     struct stat st;
485
486     int fd = vlc_open (psz_path, O_RDONLY);
487     if (fd == -1)
488         goto error;
489
490     block_t *block = block_File (fd);
491     if (block != NULL)
492     {
493         close (fd);
494
495         gnutls_datum data = {
496             .data = block->p_buffer,
497             .size = block->i_buffer,
498         };
499         int res = b_priv
500             ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
501                                                    GNUTLS_X509_FMT_PEM)
502             : gnutls_certificate_set_x509_trust_mem (cred, &data,
503                                                      GNUTLS_X509_FMT_PEM);
504         block_Release (block);
505
506         if (res < 0)
507         {
508             msg_Warn (p_this, "cannot add x509 credentials (%s): %s",
509                       psz_path, gnutls_strerror (res));
510             return VLC_EGENERIC;
511         }
512         msg_Dbg (p_this, "added %d %s(s) from %s", res,
513                  b_priv ? "key" : "certificate", psz_path);
514         return VLC_SUCCESS;
515     }
516
517     if (!fstat (fd, &st) && S_ISDIR (st.st_mode))
518     {
519         close (fd);
520         msg_Dbg (p_this, "looking recursively for x509 credentials in %s",
521                  psz_path);
522         return gnutls_Addx509Directory (p_this, cred, psz_path, b_priv);
523     }
524
525 error:
526     msg_Warn (p_this, "cannot add x509 credentials (%s): %m", psz_path);
527     if (fd != -1)
528         close (fd);
529     return VLC_EGENERIC;
530 }
531
532 #ifdef WIN32
533 static int
534 gnutls_loadOSCAList (vlc_object_t *p_this,
535                      gnutls_certificate_credentials cred)
536 {
537     HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
538     if (!hCertStore)
539     {
540         msg_Warn (p_this, "could not open the Cert SystemStore");
541         return VLC_EGENERIC;
542     }
543
544     PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
545     while( pCertContext )
546     {
547         gnutls_datum data = {
548             .data = pCertContext->pbCertEncoded,
549             .size = pCertContext->cbCertEncoded,
550         };
551
552         if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
553         {
554             msg_Warn (p_this, "cannot add x509 credential");
555             return VLC_EGENERIC;
556         }
557
558         pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
559     }
560     return VLC_SUCCESS;
561 }
562 #endif
563
564 /**
565  * Initializes a client-side TLS session.
566  */
567 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
568 {
569     if (gnutls_Init (VLC_OBJECT(session)))
570         return VLC_EGENERIC;
571
572     vlc_tls_sys_t *sys = malloc (sizeof (*sys));
573     if (unlikely(sys == NULL))
574     {
575         gnutls_Deinit (VLC_OBJECT(session));
576         return VLC_ENOMEM;
577     }
578
579     session->sys = sys;
580     session->sock.p_sys = session;
581     session->sock.pf_send = gnutls_Send;
582     session->sock.pf_recv = gnutls_Recv;
583     sys->handshaked = false;
584
585     int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
586     if (val != 0)
587     {
588         msg_Err (session, "cannot allocate X509 credentials: %s",
589                  gnutls_strerror (val));
590         goto error;
591     }
592
593     char *userdir = config_GetUserDir (VLC_DATA_DIR);
594     if (userdir != NULL)
595     {
596         char path[strlen (userdir) + sizeof ("/ssl/private")];
597         sprintf (path, "%s/ssl", userdir);
598         vlc_mkdir (path, 0755);
599
600         sprintf (path, "%s/ssl/certs", userdir);
601         gnutls_Addx509Directory (VLC_OBJECT(session), sys->x509_cred, path, false);
602         sprintf (path, "%s/ssl/private", userdir);
603         gnutls_Addx509Directory (VLC_OBJECT(session), sys->x509_cred, path, true);
604         free (userdir);
605     }
606
607 #ifdef WIN32
608     gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
609 #else
610     const char *confdir = config_GetConfDir ();
611     {
612         char path[strlen (confdir)
613                    + sizeof ("/ssl/certs/ca-certificates.crt")];
614         sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
615         gnutls_Addx509File (VLC_OBJECT(session), sys->x509_cred, path, false);
616     }
617 #endif
618     session->handshake = gnutls_HandshakeAndValidate;
619     /*session->_handshake = gnutls_ContinueHandshake;*/
620
621     val = gnutls_init (&sys->session, GNUTLS_CLIENT);
622     if (val != 0)
623     {
624         msg_Err (session, "cannot initialize TLS session: %s",
625                  gnutls_strerror (val));
626         gnutls_certificate_free_credentials (sys->x509_cred);
627         goto error;
628     }
629
630     if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
631         goto s_error;
632
633     /* minimum DH prime bits */
634     gnutls_dh_set_prime_bits (sys->session, 1024);
635
636     val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
637                                   sys->x509_cred);
638     if (val < 0)
639     {
640         msg_Err (session, "cannot set TLS session credentials: %s",
641                  gnutls_strerror (val));
642         goto s_error;
643     }
644
645     /* server name */
646     if (likely(hostname != NULL))
647     {
648         /* fill Server Name Indication */
649         gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
650                                 hostname, strlen (hostname));
651         /* keep hostname to match CNAME after handshake */
652         sys->hostname = strdup (hostname);
653         if (unlikely(sys->hostname == NULL))
654             goto s_error;
655     }
656     else
657         sys->hostname = NULL;
658
659     gnutls_transport_set_ptr (sys->session,
660                               (gnutls_transport_ptr_t)(intptr_t)fd);
661     return VLC_SUCCESS;
662
663 s_error:
664     gnutls_deinit (sys->session);
665     gnutls_certificate_free_credentials (sys->x509_cred);
666 error:
667     gnutls_Deinit (VLC_OBJECT(session));
668     free (sys);
669     return VLC_EGENERIC;
670 }
671
672
673 static void CloseClient (vlc_tls_t *session)
674 {
675     vlc_tls_sys_t *sys = session->sys;
676
677     if (sys->handshaked)
678         gnutls_bye (sys->session, GNUTLS_SHUT_WR);
679     gnutls_deinit (sys->session);
680     /* credentials must be free'd *after* gnutls_deinit() */
681     gnutls_certificate_free_credentials (sys->x509_cred);
682
683     gnutls_Deinit (VLC_OBJECT(session));
684     free (sys->hostname);
685     free (sys);
686 }
687
688
689 /**
690  * Server-side TLS
691  */
692 struct vlc_tls_creds_sys
693 {
694     gnutls_certificate_credentials_t x509_cred;
695     gnutls_dh_params_t               dh_params;
696     int                            (*handshake) (vlc_tls_t *);
697 };
698
699
700 /**
701  * Terminates TLS session and releases session data.
702  * You still have to close the socket yourself.
703  */
704 static void gnutls_SessionClose (vlc_tls_t *session)
705 {
706     vlc_tls_sys_t *sys = session->sys;
707
708     if (sys->handshaked)
709         gnutls_bye (sys->session, GNUTLS_SHUT_WR);
710     gnutls_deinit (sys->session);
711
712     vlc_object_release (session);
713     free (sys);
714 }
715
716
717 /**
718  * Initializes a server-side TLS session.
719  */
720 static vlc_tls_t *gnutls_SessionOpen (vlc_tls_creds_t *server, int fd)
721 {
722     vlc_tls_creds_sys_t *ssys = server->sys;
723     int val;
724
725     vlc_tls_t *session = vlc_object_create (server, sizeof (*session));
726     if (unlikely(session == NULL))
727         return NULL;
728
729     vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
730     if (unlikely(sys == NULL))
731     {
732         vlc_object_release (session);
733         return NULL;
734     }
735
736     session->sys = sys;
737     session->sock.p_sys = session;
738     session->sock.pf_send = gnutls_Send;
739     session->sock.pf_recv = gnutls_Recv;
740     session->handshake = ssys->handshake;
741     session->u.close = gnutls_SessionClose;
742     sys->handshaked = false;
743     sys->hostname = NULL;
744
745     val = gnutls_init (&sys->session, GNUTLS_SERVER);
746     if (val != 0)
747     {
748         msg_Err (server, "cannot initialize TLS session: %s",
749                  gnutls_strerror (val));
750         free (sys);
751         vlc_object_release (session);
752         return NULL;
753     }
754
755     if (gnutls_SessionPrioritize (VLC_OBJECT (server), sys->session))
756         goto error;
757
758     val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
759                                   ssys->x509_cred);
760     if (val < 0)
761     {
762         msg_Err (server, "cannot set TLS session credentials: %s",
763                  gnutls_strerror (val));
764         goto error;
765     }
766
767     if (session->handshake == gnutls_HandshakeAndValidate)
768         gnutls_certificate_server_set_request (sys->session,
769                                                GNUTLS_CERT_REQUIRE);
770
771     gnutls_transport_set_ptr (sys->session,
772                               (gnutls_transport_ptr_t)(intptr_t)fd);
773     return session;
774
775 error:
776     gnutls_SessionClose (session);
777     return NULL;
778 }
779
780
781 /**
782  * Adds one or more certificate authorities.
783  *
784  * @param ca_path (Unicode) path to an x509 certificates list.
785  *
786  * @return -1 on error, 0 on success.
787  */
788 static int gnutls_ServerAddCA (vlc_tls_creds_t *server, const char *ca_path)
789 {
790     vlc_tls_creds_sys_t *sys = server->sys;
791     char *local_path = ToLocale (ca_path);
792
793     int val = gnutls_certificate_set_x509_trust_file (sys->x509_cred,
794                                                       local_path,
795                                                       GNUTLS_X509_FMT_PEM );
796     LocaleFree (local_path);
797     if (val < 0)
798     {
799         msg_Err (server, "cannot add trusted CA (%s): %s", ca_path,
800                  gnutls_strerror (val));
801         return VLC_EGENERIC;
802     }
803     msg_Dbg (server, " %d trusted CA added (%s)", val, ca_path);
804
805     /* enables peer's certificate verification */
806     sys->handshake = gnutls_HandshakeAndValidate;
807
808     return VLC_SUCCESS;
809 }
810
811
812 /**
813  * Adds a certificates revocation list to be sent to TLS clients.
814  *
815  * @param crl_path (Unicode) path of the CRL file.
816  *
817  * @return -1 on error, 0 on success.
818  */
819 static int gnutls_ServerAddCRL (vlc_tls_creds_t *server, const char *crl_path)
820 {
821     vlc_tls_creds_sys_t *sys = server->sys;
822     char *local_path = ToLocale (crl_path);
823
824     int val = gnutls_certificate_set_x509_crl_file (sys->x509_cred,
825                                                     local_path,
826                                                     GNUTLS_X509_FMT_PEM);
827     LocaleFree (local_path);
828     if (val < 0)
829     {
830         msg_Err (server, "cannot add CRL (%s): %s", crl_path,
831                  gnutls_strerror (val));
832         return VLC_EGENERIC;
833     }
834     msg_Dbg (server, "%d CRL added (%s)", val, crl_path);
835     return VLC_SUCCESS;
836 }
837
838
839 /**
840  * Allocates a whole server's TLS credentials.
841  */
842 static int OpenServer (vlc_object_t *obj)
843 {
844     vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
845     int val;
846
847     if (gnutls_Init (obj))
848         return VLC_EGENERIC;
849
850     msg_Dbg (obj, "creating TLS server");
851
852     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
853     if (unlikely(sys == NULL))
854         return VLC_ENOMEM;
855
856     server->sys     = sys;
857     server->add_CA  = gnutls_ServerAddCA;
858     server->add_CRL = gnutls_ServerAddCRL;
859     server->open    = gnutls_SessionOpen;
860     /* No certificate validation by default */
861     sys->handshake  = gnutls_ContinueHandshake;
862
863     /* Sets server's credentials */
864     val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
865     if (val != 0)
866     {
867         msg_Err (server, "cannot allocate X509 credentials: %s",
868                  gnutls_strerror (val));
869         goto error;
870     }
871
872     char *cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
873     char *key_path = var_GetNonEmptyString (obj, "tls-x509-key");
874     const char *lcert = ToLocale (cert_path);
875     const char *lkey = ToLocale (key_path);
876     val = gnutls_certificate_set_x509_key_file (sys->x509_cred, lcert, lkey,
877                                                 GNUTLS_X509_FMT_PEM);
878     LocaleFree (lkey);
879     LocaleFree (lcert);
880     free (key_path);
881     free (cert_path);
882
883     if (val < 0)
884     {
885         msg_Err (server, "cannot set certificate chain or private key: %s",
886                  gnutls_strerror (val));
887         gnutls_certificate_free_credentials (sys->x509_cred);
888         goto error;
889     }
890
891     /* FIXME:
892      * - support other cipher suites
893      */
894     val = gnutls_dh_params_init (&sys->dh_params);
895     if (val >= 0)
896     {
897         const gnutls_datum_t data = {
898             .data = (unsigned char *)dh_params,
899             .size = sizeof (dh_params) - 1,
900         };
901
902         val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
903                                              GNUTLS_X509_FMT_PEM);
904         if (val == 0)
905             gnutls_certificate_set_dh_params (sys->x509_cred,
906                                               sys->dh_params);
907     }
908     if (val < 0)
909     {
910         msg_Err (server, "cannot initialize DHE cipher suites: %s",
911                  gnutls_strerror (val));
912     }
913
914     return VLC_SUCCESS;
915
916 error:
917     free (sys);
918     return VLC_EGENERIC;
919 }
920
921 /**
922  * Destroys a TLS server object.
923  */
924 static void CloseServer (vlc_object_t *obj)
925 {
926     vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
927     vlc_tls_creds_sys_t *sys = server->sys;
928
929     /* all sessions depending on the server are now deinitialized */
930     gnutls_certificate_free_credentials (sys->x509_cred);
931     gnutls_dh_params_deinit (sys->dh_params);
932     free (sys);
933
934     gnutls_Deinit (obj);
935 }