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