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