]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
gnutls: remove support for certificates and keys from .config/vlc
[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 <errno.h>
30 #include <assert.h>
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_tls.h>
35 #include <vlc_block.h>
36
37 #include <gnutls/gnutls.h>
38 #include <gnutls/x509.h>
39 #if (GNUTLS_VERSION_NUMBER < 0x030014)
40 # define gnutls_certificate_set_x509_system_trust(c) \
41     (c, GNUTLS_E_UNIMPLEMENTED_FEATURE)
42 #endif
43
44 #include "dhparams.h"
45
46 /*****************************************************************************
47  * Module descriptor
48  *****************************************************************************/
49 static int  OpenClient  (vlc_tls_t *, int, const char *);
50 static void CloseClient (vlc_tls_t *);
51 static int  OpenServer  (vlc_tls_creds_t *, const char *, const char *);
52 static void CloseServer (vlc_tls_creds_t *);
53
54 #define PRIORITIES_TEXT N_("TLS cipher priorities")
55 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
56     "hash functions and compression methods can be selected. " \
57     "Refer to GNU TLS documentation for detailed syntax.")
58 static const char *const priorities_values[] = {
59     "PERFORMANCE",
60     "NORMAL",
61     "SECURE128",
62     "SECURE256",
63     "EXPORT",
64 };
65 static const char *const priorities_text[] = {
66     N_("Performance (prioritize faster ciphers)"),
67     N_("Normal"),
68     N_("Secure 128-bits (exclude 256-bits ciphers)"),
69     N_("Secure 256-bits (prioritize 256-bits ciphers)"),
70     N_("Export (include insecure ciphers)"),
71 };
72
73 vlc_module_begin ()
74     set_shortname( "GNU TLS" )
75     set_description( N_("GNU TLS transport layer security") )
76     set_capability( "tls client", 1 )
77     set_callbacks( OpenClient, CloseClient )
78     set_category( CAT_ADVANCED )
79     set_subcategory( SUBCAT_ADVANCED_MISC )
80
81     add_submodule ()
82         set_description( N_("GNU TLS server") )
83         set_capability( "tls server", 1 )
84         set_category( CAT_ADVANCED )
85         set_subcategory( SUBCAT_ADVANCED_MISC )
86         set_callbacks( OpenServer, CloseServer )
87
88         add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
89                     PRIORITIES_LONGTEXT, false)
90             change_string_list (priorities_values, priorities_text)
91 vlc_module_end ()
92
93 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
94
95 /**
96  * Initializes GnuTLS with proper locking.
97  * @return VLC_SUCCESS on success, a VLC error code otherwise.
98  */
99 static int gnutls_Init (vlc_object_t *p_this)
100 {
101     int ret = VLC_EGENERIC;
102
103     vlc_mutex_lock (&gnutls_mutex);
104     if (gnutls_global_init ())
105     {
106         msg_Err (p_this, "cannot initialize GnuTLS");
107         goto error;
108     }
109
110     const char *psz_version = gnutls_check_version ("2.6.6");
111     if (psz_version == NULL)
112     {
113         msg_Err (p_this, "unsupported GnuTLS version");
114         gnutls_global_deinit ();
115         goto error;
116     }
117
118     msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
119     ret = VLC_SUCCESS;
120
121 error:
122     vlc_mutex_unlock (&gnutls_mutex);
123     return ret;
124 }
125
126
127 /**
128  * Deinitializes GnuTLS.
129  */
130 static void gnutls_Deinit (vlc_object_t *p_this)
131 {
132     vlc_mutex_lock (&gnutls_mutex);
133
134     gnutls_global_deinit ();
135     msg_Dbg (p_this, "GnuTLS deinitialized");
136     vlc_mutex_unlock (&gnutls_mutex);
137 }
138
139
140 static int gnutls_Error (vlc_object_t *obj, int val)
141 {
142     switch (val)
143     {
144         case GNUTLS_E_AGAIN:
145 #ifdef WIN32
146             WSASetLastError (WSAEWOULDBLOCK);
147 #else
148             errno = EAGAIN;
149 #endif
150             break;
151
152         case GNUTLS_E_INTERRUPTED:
153 #ifdef WIN32
154             WSASetLastError (WSAEINTR);
155 #else
156             errno = EINTR;
157 #endif
158             break;
159
160         default:
161             msg_Err (obj, "%s", gnutls_strerror (val));
162 #ifndef NDEBUG
163             if (!gnutls_error_is_fatal (val))
164                 msg_Err (obj, "Error above should be handled");
165 #endif
166 #ifdef WIN32
167             WSASetLastError (WSAECONNRESET);
168 #else
169             errno = ECONNRESET;
170 #endif
171     }
172     return -1;
173 }
174 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
175
176 struct vlc_tls_sys
177 {
178     gnutls_session_t session;
179     gnutls_certificate_credentials_t x509_cred;
180     char *hostname;
181     bool handshaked;
182 };
183
184
185 /**
186  * Sends data through a TLS session.
187  */
188 static int gnutls_Send (void *opaque, const void *buf, size_t length)
189 {
190     vlc_tls_t *session = opaque;
191     vlc_tls_sys_t *sys = session->sys;
192
193     int val = gnutls_record_send (sys->session, buf, length);
194     return (val < 0) ? gnutls_Error (session, val) : val;
195 }
196
197
198 /**
199  * Receives data through a TLS session.
200  */
201 static int gnutls_Recv (void *opaque, void *buf, size_t length)
202 {
203     vlc_tls_t *session = opaque;
204     vlc_tls_sys_t *sys = session->sys;
205
206     int val = gnutls_record_recv (sys->session, buf, length);
207     return (val < 0) ? gnutls_Error (session, val) : val;
208 }
209
210
211 /**
212  * Starts or continues the TLS handshake.
213  *
214  * @return -1 on fatal error, 0 on successful handshake completion,
215  * 1 if more would-be blocking recv is needed,
216  * 2 if more would-be blocking send is required.
217  */
218 static int gnutls_ContinueHandshake (vlc_tls_t *session)
219 {
220     vlc_tls_sys_t *sys = session->sys;
221     int val;
222
223 #ifdef WIN32
224     WSASetLastError (0);
225 #endif
226     val = gnutls_handshake (sys->session);
227     if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
228         return 1 + gnutls_record_get_direction (sys->session);
229
230     if (val < 0)
231     {
232 #ifdef WIN32
233         msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
234 #endif
235         msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
236         return -1;
237     }
238
239     sys->handshaked = true;
240     return 0;
241 }
242
243
244 typedef struct
245 {
246     int flag;
247     const char *msg;
248 } error_msg_t;
249
250 static const error_msg_t cert_errors[] =
251 {
252     { GNUTLS_CERT_INVALID,
253         "Certificate could not be verified" },
254     { GNUTLS_CERT_REVOKED,
255         "Certificate was revoked" },
256     { GNUTLS_CERT_SIGNER_NOT_FOUND,
257         "Certificate's signer was not found" },
258     { GNUTLS_CERT_SIGNER_NOT_CA,
259         "Certificate's signer is not a CA" },
260     { GNUTLS_CERT_INSECURE_ALGORITHM,
261         "Insecure certificate signature algorithm" },
262     { GNUTLS_CERT_NOT_ACTIVATED,
263         "Certificate is not yet activated" },
264     { GNUTLS_CERT_EXPIRED,
265         "Certificate has expired" },
266     { 0, NULL }
267 };
268
269
270 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
271 {
272     vlc_tls_sys_t *sys = session->sys;
273
274     int val = gnutls_ContinueHandshake (session);
275     if (val)
276         return val;
277
278     /* certificates chain verification */
279     unsigned status;
280
281     val = gnutls_certificate_verify_peers2 (sys->session, &status);
282     if (val)
283     {
284         msg_Err (session, "Certificate verification failed: %s",
285                  gnutls_strerror (val));
286         return -1;
287     }
288
289     if (status)
290     {
291         msg_Err (session, "TLS session: access denied (status 0x%X)", status);
292         for (const error_msg_t *e = cert_errors; e->flag; e++)
293         {
294             if (status & e->flag)
295             {
296                 msg_Err (session, "%s", e->msg);
297                 status &= ~e->flag;
298             }
299         }
300
301         if (status)
302             msg_Err (session,
303                      "unknown certificate error (you found a bug in VLC)");
304         return -1;
305     }
306
307     /* certificate (host)name verification */
308     const gnutls_datum_t *data;
309     data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
310     if (data == NULL)
311     {
312         msg_Err (session, "Peer certificate not available");
313         return -1;
314     }
315
316     gnutls_x509_crt_t cert;
317     val = gnutls_x509_crt_init (&cert);
318     if (val)
319     {
320         msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
321         return -1;
322     }
323
324     val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
325     if (val)
326     {
327         msg_Err (session, "Certificate import error: %s",
328                  gnutls_strerror (val));
329         goto error;
330     }
331
332     if (sys->hostname != NULL
333      && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
334     {
335         msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
336         goto error;
337     }
338
339     gnutls_x509_crt_deinit (cert);
340     msg_Dbg (session, "TLS/X.509 certificate verified");
341     return 0;
342
343 error:
344     gnutls_x509_crt_deinit (cert);
345     return -1;
346 }
347
348 static int
349 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
350 {
351     char *priorities = var_InheritString (obj, "gnutls-priorities");
352     if (unlikely(priorities == NULL))
353         return VLC_ENOMEM;
354
355     const char *errp;
356     int val = gnutls_priority_set_direct (session, priorities, &errp);
357     if (val < 0)
358     {
359         msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
360                  gnutls_strerror (val));
361         val = VLC_EGENERIC;
362     }
363     else
364         val = VLC_SUCCESS;
365     free (priorities);
366     return val;
367 }
368
369 /**
370  * Initializes a client-side TLS session.
371  */
372 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
373 {
374     if (gnutls_Init (VLC_OBJECT(session)))
375         return VLC_EGENERIC;
376
377     vlc_tls_sys_t *sys = malloc (sizeof (*sys));
378     if (unlikely(sys == NULL))
379     {
380         gnutls_Deinit (VLC_OBJECT(session));
381         return VLC_ENOMEM;
382     }
383
384     session->sys = sys;
385     session->sock.p_sys = session;
386     session->sock.pf_send = gnutls_Send;
387     session->sock.pf_recv = gnutls_Recv;
388     sys->handshaked = false;
389
390     int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
391     if (val != 0)
392     {
393         msg_Err (session, "cannot allocate credentials: %s",
394                  gnutls_strerror (val));
395         goto error;
396     }
397
398     val = gnutls_certificate_set_x509_system_trust (sys->x509_cred);
399     if (val < 0)
400         msg_Err (session, "cannot load trusted Certificate Authorities: %s",
401                  gnutls_strerror (val));
402     else
403         msg_Dbg (session, "loaded %d trusted CAs", val);
404
405     gnutls_certificate_set_verify_flags (sys->x509_cred,
406                                          GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
407
408     session->handshake = gnutls_HandshakeAndValidate;
409     /*session->_handshake = gnutls_ContinueHandshake;*/
410
411     val = gnutls_init (&sys->session, GNUTLS_CLIENT);
412     if (val != 0)
413     {
414         msg_Err (session, "cannot initialize TLS session: %s",
415                  gnutls_strerror (val));
416         gnutls_certificate_free_credentials (sys->x509_cred);
417         goto error;
418     }
419
420     if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
421         goto s_error;
422
423     /* minimum DH prime bits */
424     gnutls_dh_set_prime_bits (sys->session, 1024);
425
426     val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
427                                   sys->x509_cred);
428     if (val < 0)
429     {
430         msg_Err (session, "cannot set TLS session credentials: %s",
431                  gnutls_strerror (val));
432         goto s_error;
433     }
434
435     /* server name */
436     if (likely(hostname != NULL))
437     {
438         /* fill Server Name Indication */
439         gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
440                                 hostname, strlen (hostname));
441         /* keep hostname to match CNAME after handshake */
442         sys->hostname = strdup (hostname);
443         if (unlikely(sys->hostname == NULL))
444             goto s_error;
445     }
446     else
447         sys->hostname = NULL;
448
449     gnutls_transport_set_ptr (sys->session,
450                               (gnutls_transport_ptr_t)(intptr_t)fd);
451     return VLC_SUCCESS;
452
453 s_error:
454     gnutls_deinit (sys->session);
455     gnutls_certificate_free_credentials (sys->x509_cred);
456 error:
457     gnutls_Deinit (VLC_OBJECT(session));
458     free (sys);
459     return VLC_EGENERIC;
460 }
461
462
463 static void CloseClient (vlc_tls_t *session)
464 {
465     vlc_tls_sys_t *sys = session->sys;
466
467     if (sys->handshaked)
468         gnutls_bye (sys->session, GNUTLS_SHUT_WR);
469     gnutls_deinit (sys->session);
470     /* credentials must be free'd *after* gnutls_deinit() */
471     gnutls_certificate_free_credentials (sys->x509_cred);
472
473     gnutls_Deinit (VLC_OBJECT(session));
474     free (sys->hostname);
475     free (sys);
476 }
477
478
479 /**
480  * Server-side TLS
481  */
482 struct vlc_tls_creds_sys
483 {
484     gnutls_certificate_credentials_t x509_cred;
485     gnutls_dh_params_t               dh_params;
486     int                            (*handshake) (vlc_tls_t *);
487 };
488
489
490 /**
491  * Terminates TLS session and releases session data.
492  * You still have to close the socket yourself.
493  */
494 static void gnutls_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session)
495 {
496     vlc_tls_sys_t *sys = session->sys;
497
498     if (sys->handshaked)
499         gnutls_bye (sys->session, GNUTLS_SHUT_WR);
500     gnutls_deinit (sys->session);
501
502     free (sys);
503     (void) crd;
504 }
505
506
507 /**
508  * Initializes a server-side TLS session.
509  */
510 static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
511                                int fd)
512 {
513     vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
514     if (unlikely(sys == NULL))
515         return VLC_ENOMEM;
516
517     session->sys = sys;
518     session->sock.p_sys = session;
519     session->sock.pf_send = gnutls_Send;
520     session->sock.pf_recv = gnutls_Recv;
521     session->handshake = crd->sys->handshake;
522     sys->handshaked = false;
523     sys->hostname = NULL;
524
525     int val = gnutls_init (&sys->session, GNUTLS_SERVER);
526     if (val != 0)
527     {
528         msg_Err (session, "cannot initialize TLS session: %s",
529                  gnutls_strerror (val));
530         free (sys);
531         return VLC_EGENERIC;
532     }
533
534     if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
535         goto error;
536
537     val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
538                                   crd->sys->x509_cred);
539     if (val < 0)
540     {
541         msg_Err (session, "cannot set TLS session credentials: %s",
542                  gnutls_strerror (val));
543         goto error;
544     }
545
546     if (session->handshake == gnutls_HandshakeAndValidate)
547         gnutls_certificate_server_set_request (sys->session,
548                                                GNUTLS_CERT_REQUIRE);
549
550     gnutls_transport_set_ptr (sys->session,
551                               (gnutls_transport_ptr_t)(intptr_t)fd);
552     return VLC_SUCCESS;
553
554 error:
555     gnutls_SessionClose (crd, session);
556     return VLC_EGENERIC;
557 }
558
559
560 /**
561  * Adds one or more Certificate Authorities to the trusted set.
562  *
563  * @param path (UTF-8) path to an X.509 certificates list.
564  *
565  * @return -1 on error, 0 on success.
566  */
567 static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
568 {
569     block_t *block = block_FilePath (path);
570     if (block == NULL)
571     {
572         msg_Err (crd, "cannot read trusted CA from %s: %m", path);
573         return VLC_EGENERIC;
574     }
575
576     gnutls_datum_t d = {
577        .data = block->p_buffer,
578        .size = block->i_buffer,
579     };
580
581     int val = gnutls_certificate_set_x509_trust_mem (crd->sys->x509_cred, &d,
582                                                      GNUTLS_X509_FMT_PEM);
583     block_Release (block);
584     if (val < 0)
585     {
586         msg_Err (crd, "cannot load trusted CA from %s: %s", path,
587                  gnutls_strerror (val));
588         return VLC_EGENERIC;
589     }
590     msg_Dbg (crd, " %d trusted CA%s added from %s", val, (val != 1) ? "s" : "",
591              path);
592
593     /* enables peer's certificate verification */
594     crd->sys->handshake = gnutls_HandshakeAndValidate;
595     return VLC_SUCCESS;
596 }
597
598
599 /**
600  * Adds a Certificates Revocation List to be sent to TLS clients.
601  *
602  * @param path (UTF-8) path of the CRL file.
603  *
604  * @return -1 on error, 0 on success.
605  */
606 static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
607 {
608     block_t *block = block_FilePath (path);
609     if (block == NULL)
610     {
611         msg_Err (crd, "cannot read CRL from %s: %m", path);
612         return VLC_EGENERIC;
613     }
614
615     gnutls_datum_t d = {
616        .data = block->p_buffer,
617        .size = block->i_buffer,
618     };
619
620     int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
621                                                    GNUTLS_X509_FMT_PEM);
622     block_Release (block);
623     if (val < 0)
624     {
625         msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
626         return VLC_EGENERIC;
627     }
628     msg_Dbg (crd, "%d CRL%s added from %s", val, (val != 1) ? "s" : "", path);
629     return VLC_SUCCESS;
630 }
631
632
633 /**
634  * Allocates a whole server's TLS credentials.
635  */
636 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
637 {
638     int val;
639
640     if (gnutls_Init (VLC_OBJECT(crd)))
641         return VLC_EGENERIC;
642
643     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
644     if (unlikely(sys == NULL))
645         goto error;
646
647     crd->sys     = sys;
648     crd->add_CA  = gnutls_AddCA;
649     crd->add_CRL = gnutls_AddCRL;
650     crd->open    = gnutls_SessionOpen;
651     crd->close   = gnutls_SessionClose;
652     /* No certificate validation by default */
653     sys->handshake  = gnutls_ContinueHandshake;
654
655     /* Sets server's credentials */
656     val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
657     if (val != 0)
658     {
659         msg_Err (crd, "cannot allocate credentials: %s",
660                  gnutls_strerror (val));
661         goto error;
662     }
663
664     block_t *certblock = block_FilePath (cert);
665     if (certblock == NULL)
666     {
667         msg_Err (crd, "cannot read certificate chain from %s: %m", cert);
668         return VLC_EGENERIC;
669     }
670
671     block_t *keyblock = block_FilePath (key);
672     if (keyblock == NULL)
673     {
674         msg_Err (crd, "cannot read private key from %s: %m", key);
675         block_Release (certblock);
676         return VLC_EGENERIC;
677     }
678
679     gnutls_datum_t pub = {
680        .data = certblock->p_buffer,
681        .size = certblock->i_buffer,
682     }, priv = {
683        .data = keyblock->p_buffer,
684        .size = keyblock->i_buffer,
685     };
686
687     val = gnutls_certificate_set_x509_key_mem (sys->x509_cred, &pub, &priv,
688                                                 GNUTLS_X509_FMT_PEM);
689     block_Release (keyblock);
690     block_Release (certblock);
691     if (val < 0)
692     {
693         msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
694         gnutls_certificate_free_credentials (sys->x509_cred);
695         goto error;
696     }
697
698     /* FIXME:
699      * - support other cipher suites
700      */
701     val = gnutls_dh_params_init (&sys->dh_params);
702     if (val >= 0)
703     {
704         const gnutls_datum_t data = {
705             .data = (unsigned char *)dh_params,
706             .size = sizeof (dh_params) - 1,
707         };
708
709         val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
710                                              GNUTLS_X509_FMT_PEM);
711         if (val == 0)
712             gnutls_certificate_set_dh_params (sys->x509_cred,
713                                               sys->dh_params);
714     }
715     if (val < 0)
716     {
717         msg_Err (crd, "cannot initialize DHE cipher suites: %s",
718                  gnutls_strerror (val));
719     }
720
721     return VLC_SUCCESS;
722
723 error:
724     free (sys);
725     gnutls_Deinit (VLC_OBJECT(crd));
726     return VLC_EGENERIC;
727 }
728
729 /**
730  * Destroys a TLS server object.
731  */
732 static void CloseServer (vlc_tls_creds_t *crd)
733 {
734     vlc_tls_creds_sys_t *sys = crd->sys;
735
736     /* all sessions depending on the server are now deinitialized */
737     gnutls_certificate_free_credentials (sys->x509_cred);
738     gnutls_dh_params_deinit (sys->dh_params);
739     free (sys);
740
741     gnutls_Deinit (VLC_OBJECT(crd));
742 }