]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
gnutls: remove useless per-session structure
[vlc] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * gnutls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2014 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 Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Öesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <time.h>
26 #include <errno.h>
27 #include <assert.h>
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_tls.h>
32 #include <vlc_block.h>
33 #include <vlc_dialog.h>
34
35 #include <gnutls/gnutls.h>
36 #include <gnutls/x509.h>
37 #include "dhparams.h"
38
39 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
40
41 /**
42  * Initializes GnuTLS with proper locking.
43  * @return VLC_SUCCESS on success, a VLC error code otherwise.
44  */
45 static int gnutls_Init (vlc_object_t *p_this)
46 {
47     int ret = VLC_EGENERIC;
48
49     vlc_mutex_lock (&gnutls_mutex);
50     if (gnutls_global_init ())
51     {
52         msg_Err (p_this, "cannot initialize GnuTLS");
53         goto error;
54     }
55
56     const char *psz_version = gnutls_check_version ("3.1.4");
57     if (psz_version == NULL)
58     {
59         msg_Err (p_this, "unsupported GnuTLS version");
60         gnutls_global_deinit ();
61         goto error;
62     }
63
64     msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
65     ret = VLC_SUCCESS;
66
67 error:
68     vlc_mutex_unlock (&gnutls_mutex);
69     return ret;
70 }
71
72
73 /**
74  * Deinitializes GnuTLS.
75  */
76 static void gnutls_Deinit (vlc_object_t *p_this)
77 {
78     vlc_mutex_lock (&gnutls_mutex);
79
80     gnutls_global_deinit ();
81     msg_Dbg (p_this, "GnuTLS deinitialized");
82     vlc_mutex_unlock (&gnutls_mutex);
83 }
84
85
86 static int gnutls_Error (vlc_object_t *obj, int val)
87 {
88     switch (val)
89     {
90         case GNUTLS_E_AGAIN:
91 #ifdef _WIN32
92             WSASetLastError (WSAEWOULDBLOCK);
93 #else
94             errno = EAGAIN;
95 #endif
96             break;
97
98         case GNUTLS_E_INTERRUPTED:
99 #ifdef _WIN32
100             WSASetLastError (WSAEINTR);
101 #else
102             errno = EINTR;
103 #endif
104             break;
105
106         default:
107             msg_Err (obj, "%s", gnutls_strerror (val));
108 #ifndef NDEBUG
109             if (!gnutls_error_is_fatal (val))
110                 msg_Err (obj, "Error above should be handled");
111 #endif
112 #ifdef _WIN32
113             WSASetLastError (WSAECONNRESET);
114 #else
115             errno = ECONNRESET;
116 #endif
117     }
118     return -1;
119 }
120 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
121
122 /**
123  * Sends data through a TLS session.
124  */
125 static int gnutls_Send (void *opaque, const void *buf, size_t length)
126 {
127     assert (opaque != NULL);
128
129     vlc_tls_t *tls = opaque;
130     gnutls_session_t session = tls->sys;
131
132     int val = gnutls_record_send (session, buf, length);
133     return (val < 0) ? gnutls_Error (tls, val) : val;
134 }
135
136
137 /**
138  * Receives data through a TLS session.
139  */
140 static int gnutls_Recv (void *opaque, void *buf, size_t length)
141 {
142     assert (opaque != NULL);
143
144     vlc_tls_t *tls = opaque;
145     gnutls_session_t session = tls->sys;
146
147     int val = gnutls_record_recv (session, buf, length);
148     return (val < 0) ? gnutls_Error (tls, val) : val;
149 }
150
151 static int gnutls_SessionOpen (vlc_tls_t *tls, int type,
152                                gnutls_certificate_credentials_t x509, int fd)
153 {
154     gnutls_session_t session;
155     const char *errp;
156     int val;
157
158     val = gnutls_init (&session, type);
159     if (val != 0)
160     {
161         msg_Err (tls, "cannot initialize TLS session: %s",
162                  gnutls_strerror (val));
163         return VLC_EGENERIC;
164     }
165
166     char *priorities = var_InheritString (tls, "gnutls-priorities");
167     if (unlikely(priorities == NULL))
168         goto error;
169
170     val = gnutls_priority_set_direct (session, priorities, &errp);
171     if (val < 0)
172         msg_Err (tls, "cannot set TLS priorities \"%s\": %s", errp,
173                  gnutls_strerror (val));
174     free (priorities);
175     if (val < 0)
176         goto error;
177
178     val = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509);
179     if (val < 0)
180     {
181         msg_Err (tls, "cannot set TLS session credentials: %s",
182                  gnutls_strerror (val));
183         goto error;
184     }
185
186     gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t)(intptr_t)fd);
187
188     tls->sys = session;
189     tls->sock.p_sys = NULL;
190     tls->sock.pf_send = gnutls_Send;
191     tls->sock.pf_recv = gnutls_Recv;
192     return VLC_SUCCESS;
193
194 error:
195     gnutls_deinit (session);
196     return VLC_EGENERIC;
197 }
198
199 /**
200  * Starts or continues the TLS handshake.
201  *
202  * @return -1 on fatal error, 0 on successful handshake completion,
203  * 1 if more would-be blocking recv is needed,
204  * 2 if more would-be blocking send is required.
205  */
206 static int gnutls_ContinueHandshake (vlc_tls_t *tls)
207 {
208     gnutls_session_t session = tls->sys;
209     int val;
210
211 #ifdef _WIN32
212     WSASetLastError (0);
213 #endif
214     do
215     {
216         val = gnutls_handshake (session);
217         msg_Dbg (tls, "TLS handshake: %s", gnutls_strerror (val));
218
219         switch (val)
220         {
221             case GNUTLS_E_SUCCESS:
222                 return 0;
223             case GNUTLS_E_AGAIN:
224             case GNUTLS_E_INTERRUPTED:
225                 /* I/O event: return to caller's poll() loop */
226                 return 1 + gnutls_record_get_direction (session);
227         }
228     }
229     while (!gnutls_error_is_fatal (val));
230
231 #ifdef _WIN32
232     msg_Dbg (tls, "Winsock error %d", WSAGetLastError ());
233 #endif
234     msg_Err (tls, "TLS handshake error: %s", gnutls_strerror (val));
235     return -1;
236 }
237
238 /**
239  * Terminates TLS session and releases session data.
240  * You still have to close the socket yourself.
241  */
242 static void gnutls_SessionClose (vlc_tls_t *tls)
243 {
244     gnutls_session_t session = tls->sys;
245
246     if (tls->sock.p_sys != NULL)
247         gnutls_bye (session, GNUTLS_SHUT_WR);
248
249     gnutls_deinit (session);
250 }
251
252 static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
253                                      int fd, const char *hostname)
254 {
255     int val = gnutls_SessionOpen (tls, GNUTLS_CLIENT, crd->sys, fd);
256     if (val != VLC_SUCCESS)
257         return val;
258
259     gnutls_session_t session = tls->sys;
260
261     /* minimum DH prime bits */
262     gnutls_dh_set_prime_bits (session, 1024);
263
264     if (likely(hostname != NULL))
265         /* fill Server Name Indication */
266         gnutls_server_name_set (session, GNUTLS_NAME_DNS,
267                                 hostname, strlen (hostname));
268
269     return VLC_SUCCESS;
270 }
271
272 static int gnutls_ClientHandshake (vlc_tls_t *tls, const char *host,
273                                    const char *service)
274 {
275     int val = gnutls_ContinueHandshake (tls);
276     if (val)
277         return val;
278
279     /* certificates chain verification */
280     gnutls_session_t session = tls->sys;
281     unsigned status;
282
283     val = gnutls_certificate_verify_peers3 (session, host, &status);
284     if (val)
285     {
286         msg_Err (tls, "Certificate verification error: %s",
287                  gnutls_strerror (val));
288 failure:
289         gnutls_bye (session, GNUTLS_SHUT_RDWR);
290         return -1;
291     }
292
293     if (status == 0)
294     {   /* Good certificate */
295 success:
296         tls->sock.p_sys = tls;
297         return 0;
298     }
299
300     /* Bad certificate */
301     gnutls_datum_t desc;
302
303     if (gnutls_certificate_verification_status_print(status,
304                          gnutls_certificate_type_get (session), &desc, 0) == 0)
305     {
306         msg_Err (tls, "Certificate verification failure: %s", desc.data);
307         gnutls_free (desc.data);
308     }
309
310     status &= ~GNUTLS_CERT_INVALID; /* always set / catch-all error */
311     status &= ~GNUTLS_CERT_SIGNER_NOT_FOUND; /* unknown CA */
312     status &= ~GNUTLS_CERT_UNEXPECTED_OWNER; /* mismatched hostname */
313
314     if (status != 0 || host == NULL)
315         goto failure; /* Really bad certificate */
316
317     /* Look up mismatching certificate in store */
318     const gnutls_datum_t *datum;
319     unsigned count;
320
321     datum = gnutls_certificate_get_peers (session, &count);
322     if (datum == NULL || count == 0)
323     {
324         msg_Err (tls, "Peer certificate not available");
325         goto failure;
326     }
327
328     msg_Dbg (tls, "%u certificate(s) in the list", count);
329     val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
330                                        GNUTLS_CRT_X509, datum, 0);
331     const char *msg;
332     switch (val)
333     {
334         case 0:
335             msg_Dbg (tls, "certificate key match for %s", host);
336             goto success;
337         case GNUTLS_E_NO_CERTIFICATE_FOUND:
338             msg_Dbg (tls, "no known certificates for %s", host);
339             msg = N_("However the security certificate presented by the "
340                 "server is unknown and could not be authenticated by any "
341                 "trusted Certificate Authority.");
342             break;
343         case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
344             msg_Dbg (tls, "certificate keys mismatch for %s", host);
345             msg = N_("However the security certificate presented by the "
346                 "server changed since the previous visit and was not "
347                 "authenticated by any trusted Certificate Authority. ");
348             break;
349         default:
350             msg_Err (tls, "certificate key match error for %s: %s", host,
351                      gnutls_strerror (val));
352             goto failure;
353     }
354
355     if (dialog_Question (tls, _("Insecure site"),
356         _("You attempted to reach %s. %s\n"
357           "This problem may be stem from an attempt to breach your security, "
358           "compromise your privacy, or a configuration error.\n\n"
359           "If in doubt, abort now.\n"),
360                          _("Abort"), _("View certificate"), NULL,
361                          vlc_gettext (msg), host) != 2)
362         goto failure;
363
364     gnutls_x509_crt_t cert;
365
366     if (gnutls_x509_crt_init (&cert))
367         goto failure;
368     if (gnutls_x509_crt_import (cert, datum, GNUTLS_X509_FMT_DER)
369      || gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &desc))
370     {
371         gnutls_x509_crt_deinit (cert);
372         goto failure;
373     }
374     gnutls_x509_crt_deinit (cert);
375
376     val = dialog_Question (tls, _("Insecure site"),
377          _("This is the certificate presented by %s:\n%s\n\n"
378            "If in doubt, abort now.\n"),
379                            _("Abort"), _("Accept 24 hours"),
380                            _("Accept permanently"), host, desc.data);
381     gnutls_free (desc.data);
382
383     time_t expiry = 0;
384     switch (val)
385     {
386         case 2:
387             time (&expiry);
388             expiry += 24 * 60 * 60;
389         case 3:
390             val = gnutls_store_pubkey (NULL, NULL, host, service,
391                                        GNUTLS_CRT_X509, datum, expiry, 0);
392             if (val)
393                 msg_Err (tls, "cannot store X.509 certificate: %s",
394                          gnutls_strerror (val));
395             goto success;
396     }
397     goto failure;
398 }
399
400 /**
401  * Initializes a client-side TLS credentials.
402  */
403 static int OpenClient (vlc_tls_creds_t *crd)
404 {
405     gnutls_certificate_credentials_t x509;
406
407     if (gnutls_Init (VLC_OBJECT(crd)))
408         return VLC_EGENERIC;
409
410     int val = gnutls_certificate_allocate_credentials (&x509);
411     if (val != 0)
412     {
413         msg_Err (crd, "cannot allocate credentials: %s",
414                  gnutls_strerror (val));
415         gnutls_Deinit (VLC_OBJECT(crd));
416         return VLC_EGENERIC;
417     }
418
419     val = gnutls_certificate_set_x509_system_trust (x509);
420     if (val < 0)
421         msg_Err (crd, "cannot load trusted Certificate Authorities: %s",
422                  gnutls_strerror (val));
423     else
424         msg_Dbg (crd, "loaded %d trusted CAs", val);
425
426     gnutls_certificate_set_verify_flags (x509,
427                                          GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
428
429     crd->sys = x509;
430     crd->open = gnutls_ClientSessionOpen;
431     crd->handshake = gnutls_ClientHandshake;
432     crd->close = gnutls_SessionClose;
433
434     return VLC_SUCCESS;
435 }
436
437 static void CloseClient (vlc_tls_creds_t *crd)
438 {
439     gnutls_certificate_credentials_t x509 = crd->sys;
440
441     gnutls_certificate_free_credentials (x509);
442
443     gnutls_Deinit (VLC_OBJECT(crd));
444 }
445
446 #ifdef ENABLE_SOUT
447 /**
448  * Server-side TLS credentials private data
449  */
450 typedef struct vlc_tls_creds_sys
451 {
452     gnutls_certificate_credentials_t x509_cred;
453     gnutls_dh_params_t dh_params;
454 } vlc_tls_creds_sys_t;
455
456 /**
457  * Initializes a server-side TLS session.
458  */
459 static int gnutls_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
460                                      int fd, const char *hostname)
461 {
462     vlc_tls_creds_sys_t *sys = crd->sys;
463
464     assert (hostname == NULL);
465     return gnutls_SessionOpen (tls, GNUTLS_SERVER, sys->x509_cred, fd);
466 }
467
468 static int gnutls_ServerHandshake (vlc_tls_t *tls, const char *host,
469                                    const char *service)
470 {
471     int val = gnutls_ContinueHandshake (tls);
472     if (val == 0)
473         tls->sock.p_sys = tls;
474
475     (void) host; (void) service;
476     return val;
477 }
478
479 /**
480  * Allocates a whole server's TLS credentials.
481  */
482 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
483 {
484     int val;
485
486     if (gnutls_Init (VLC_OBJECT(crd)))
487         return VLC_EGENERIC;
488
489     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
490     if (unlikely(sys == NULL))
491         goto error;
492
493     /* Sets server's credentials */
494     val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
495     if (val != 0)
496     {
497         msg_Err (crd, "cannot allocate credentials: %s",
498                  gnutls_strerror (val));
499         goto error;
500     }
501
502     block_t *certblock = block_FilePath (cert);
503     if (certblock == NULL)
504     {
505         msg_Err (crd, "cannot read certificate chain from %s: %s", cert,
506                  vlc_strerror_c(errno));
507         return VLC_EGENERIC;
508     }
509
510     block_t *keyblock = block_FilePath (key);
511     if (keyblock == NULL)
512     {
513         msg_Err (crd, "cannot read private key from %s: %s", key,
514                  vlc_strerror_c(errno));
515         block_Release (certblock);
516         return VLC_EGENERIC;
517     }
518
519     gnutls_datum_t pub = {
520        .data = certblock->p_buffer,
521        .size = certblock->i_buffer,
522     }, priv = {
523        .data = keyblock->p_buffer,
524        .size = keyblock->i_buffer,
525     };
526
527     val = gnutls_certificate_set_x509_key_mem (sys->x509_cred, &pub, &priv,
528                                                 GNUTLS_X509_FMT_PEM);
529     block_Release (keyblock);
530     block_Release (certblock);
531     if (val < 0)
532     {
533         msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
534         gnutls_certificate_free_credentials (sys->x509_cred);
535         goto error;
536     }
537
538     /* FIXME:
539      * - support other cipher suites
540      */
541     val = gnutls_dh_params_init (&sys->dh_params);
542     if (val >= 0)
543     {
544         const gnutls_datum_t data = {
545             .data = (unsigned char *)dh_params,
546             .size = sizeof (dh_params) - 1,
547         };
548
549         val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
550                                              GNUTLS_X509_FMT_PEM);
551         if (val == 0)
552             gnutls_certificate_set_dh_params (sys->x509_cred,
553                                               sys->dh_params);
554     }
555     if (val < 0)
556     {
557         msg_Err (crd, "cannot initialize DHE cipher suites: %s",
558                  gnutls_strerror (val));
559     }
560
561     crd->sys = sys;
562     crd->open = gnutls_ServerSessionOpen;
563     crd->handshake = gnutls_ServerHandshake;
564     crd->close = gnutls_SessionClose;
565
566     return VLC_SUCCESS;
567
568 error:
569     free (sys);
570     gnutls_Deinit (VLC_OBJECT(crd));
571     return VLC_EGENERIC;
572 }
573
574 /**
575  * Destroys a TLS server object.
576  */
577 static void CloseServer (vlc_tls_creds_t *crd)
578 {
579     vlc_tls_creds_sys_t *sys = crd->sys;
580
581     /* all sessions depending on the server are now deinitialized */
582     gnutls_certificate_free_credentials (sys->x509_cred);
583     gnutls_dh_params_deinit (sys->dh_params);
584     free (sys);
585
586     gnutls_Deinit (VLC_OBJECT(crd));
587 }
588 #endif
589
590 #define PRIORITIES_TEXT N_("TLS cipher priorities")
591 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
592     "hash functions and compression methods can be selected. " \
593     "Refer to GNU TLS documentation for detailed syntax.")
594 static const char *const priorities_values[] = {
595     "PERFORMANCE",
596     "NORMAL",
597     "SECURE128",
598     "SECURE256",
599     "EXPORT",
600 };
601 static const char *const priorities_text[] = {
602     N_("Performance (prioritize faster ciphers)"),
603     N_("Normal"),
604     N_("Secure 128-bits (exclude 256-bits ciphers)"),
605     N_("Secure 256-bits (prioritize 256-bits ciphers)"),
606     N_("Export (include insecure ciphers)"),
607 };
608
609 vlc_module_begin ()
610     set_shortname( "GNU TLS" )
611     set_description( N_("GNU TLS transport layer security") )
612     set_capability( "tls client", 1 )
613     set_callbacks( OpenClient, CloseClient )
614     set_category( CAT_ADVANCED )
615     set_subcategory( SUBCAT_ADVANCED_NETWORK )
616     add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
617                 PRIORITIES_LONGTEXT, false)
618         change_string_list (priorities_values, priorities_text)
619 #ifdef ENABLE_SOUT
620     add_submodule ()
621         set_description( N_("GNU TLS server") )
622         set_capability( "tls server", 1 )
623         set_category( CAT_ADVANCED )
624         set_subcategory( SUBCAT_ADVANCED_NETWORK )
625         set_callbacks( OpenServer, CloseServer )
626 #endif
627 vlc_module_end ()