1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2004-2011 RĂ©mi Denis-Courmont
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.
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.
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 *****************************************************************************/
21 /*****************************************************************************
23 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
35 #include <vlc_block.h>
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)
46 /*****************************************************************************
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 *);
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[] = {
65 static const char *const priorities_text[] = {
66 N_("Performance (prioritize faster ciphers)"),
68 N_("Secure 128-bits (exclude 256-bits ciphers)"),
69 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
70 N_("Export (include insecure ciphers)"),
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 )
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 )
88 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
89 PRIORITIES_LONGTEXT, false)
90 change_string_list (priorities_values, priorities_text)
93 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
96 * Initializes GnuTLS with proper locking.
97 * @return VLC_SUCCESS on success, a VLC error code otherwise.
99 static int gnutls_Init (vlc_object_t *p_this)
101 int ret = VLC_EGENERIC;
103 vlc_mutex_lock (&gnutls_mutex);
104 if (gnutls_global_init ())
106 msg_Err (p_this, "cannot initialize GnuTLS");
110 const char *psz_version = gnutls_check_version ("2.6.6");
111 if (psz_version == NULL)
113 msg_Err (p_this, "unsupported GnuTLS version");
114 gnutls_global_deinit ();
118 msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
122 vlc_mutex_unlock (&gnutls_mutex);
128 * Deinitializes GnuTLS.
130 static void gnutls_Deinit (vlc_object_t *p_this)
132 vlc_mutex_lock (&gnutls_mutex);
134 gnutls_global_deinit ();
135 msg_Dbg (p_this, "GnuTLS deinitialized");
136 vlc_mutex_unlock (&gnutls_mutex);
140 static int gnutls_Error (vlc_object_t *obj, int val)
146 WSASetLastError (WSAEWOULDBLOCK);
152 case GNUTLS_E_INTERRUPTED:
154 WSASetLastError (WSAEINTR);
161 msg_Err (obj, "%s", gnutls_strerror (val));
163 if (!gnutls_error_is_fatal (val))
164 msg_Err (obj, "Error above should be handled");
167 WSASetLastError (WSAECONNRESET);
174 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
178 gnutls_session_t session;
179 gnutls_certificate_credentials_t x509_cred;
186 * Sends data through a TLS session.
188 static int gnutls_Send (void *opaque, const void *buf, size_t length)
190 vlc_tls_t *session = opaque;
191 vlc_tls_sys_t *sys = session->sys;
193 int val = gnutls_record_send (sys->session, buf, length);
194 return (val < 0) ? gnutls_Error (session, val) : val;
199 * Receives data through a TLS session.
201 static int gnutls_Recv (void *opaque, void *buf, size_t length)
203 vlc_tls_t *session = opaque;
204 vlc_tls_sys_t *sys = session->sys;
206 int val = gnutls_record_recv (sys->session, buf, length);
207 return (val < 0) ? gnutls_Error (session, val) : val;
212 * Starts or continues the TLS handshake.
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.
218 static int gnutls_ContinueHandshake (vlc_tls_t *session)
220 vlc_tls_sys_t *sys = session->sys;
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);
233 msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
235 msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
239 sys->handshaked = true;
250 static const error_msg_t cert_errors[] =
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" },
270 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
272 vlc_tls_sys_t *sys = session->sys;
274 int val = gnutls_ContinueHandshake (session);
278 /* certificates chain verification */
281 val = gnutls_certificate_verify_peers2 (sys->session, &status);
284 msg_Err (session, "Certificate verification failed: %s",
285 gnutls_strerror (val));
291 msg_Err (session, "TLS session: access denied (status 0x%X)", status);
292 for (const error_msg_t *e = cert_errors; e->flag; e++)
294 if (status & e->flag)
296 msg_Err (session, "%s", e->msg);
303 "unknown certificate error (you found a bug in VLC)");
307 /* certificate (host)name verification */
308 const gnutls_datum_t *data;
309 data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
312 msg_Err (session, "Peer certificate not available");
316 gnutls_x509_crt_t cert;
317 val = gnutls_x509_crt_init (&cert);
320 msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
324 val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
327 msg_Err (session, "Certificate import error: %s",
328 gnutls_strerror (val));
332 if (sys->hostname != NULL
333 && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
335 msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
339 gnutls_x509_crt_deinit (cert);
340 msg_Dbg (session, "TLS/X.509 certificate verified");
344 gnutls_x509_crt_deinit (cert);
349 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
351 char *priorities = var_InheritString (obj, "gnutls-priorities");
352 if (unlikely(priorities == NULL))
356 int val = gnutls_priority_set_direct (session, priorities, &errp);
359 msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
360 gnutls_strerror (val));
370 * Initializes a client-side TLS session.
372 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
374 if (gnutls_Init (VLC_OBJECT(session)))
377 vlc_tls_sys_t *sys = malloc (sizeof (*sys));
378 if (unlikely(sys == NULL))
380 gnutls_Deinit (VLC_OBJECT(session));
385 session->sock.p_sys = session;
386 session->sock.pf_send = gnutls_Send;
387 session->sock.pf_recv = gnutls_Recv;
388 sys->handshaked = false;
390 int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
393 msg_Err (session, "cannot allocate credentials: %s",
394 gnutls_strerror (val));
398 val = gnutls_certificate_set_x509_system_trust (sys->x509_cred);
400 msg_Err (session, "cannot load trusted Certificate Authorities: %s",
401 gnutls_strerror (val));
403 msg_Dbg (session, "loaded %d trusted CAs", val);
405 gnutls_certificate_set_verify_flags (sys->x509_cred,
406 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
408 session->handshake = gnutls_HandshakeAndValidate;
409 /*session->_handshake = gnutls_ContinueHandshake;*/
411 val = gnutls_init (&sys->session, GNUTLS_CLIENT);
414 msg_Err (session, "cannot initialize TLS session: %s",
415 gnutls_strerror (val));
416 gnutls_certificate_free_credentials (sys->x509_cred);
420 if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
423 /* minimum DH prime bits */
424 gnutls_dh_set_prime_bits (sys->session, 1024);
426 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
430 msg_Err (session, "cannot set TLS session credentials: %s",
431 gnutls_strerror (val));
436 if (likely(hostname != NULL))
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))
447 sys->hostname = NULL;
449 gnutls_transport_set_ptr (sys->session,
450 (gnutls_transport_ptr_t)(intptr_t)fd);
454 gnutls_deinit (sys->session);
455 gnutls_certificate_free_credentials (sys->x509_cred);
457 gnutls_Deinit (VLC_OBJECT(session));
463 static void CloseClient (vlc_tls_t *session)
465 vlc_tls_sys_t *sys = session->sys;
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);
473 gnutls_Deinit (VLC_OBJECT(session));
474 free (sys->hostname);
482 struct vlc_tls_creds_sys
484 gnutls_certificate_credentials_t x509_cred;
485 gnutls_dh_params_t dh_params;
486 int (*handshake) (vlc_tls_t *);
491 * Terminates TLS session and releases session data.
492 * You still have to close the socket yourself.
494 static void gnutls_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session)
496 vlc_tls_sys_t *sys = session->sys;
499 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
500 gnutls_deinit (sys->session);
508 * Initializes a server-side TLS session.
510 static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
513 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
514 if (unlikely(sys == NULL))
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;
525 int val = gnutls_init (&sys->session, GNUTLS_SERVER);
528 msg_Err (session, "cannot initialize TLS session: %s",
529 gnutls_strerror (val));
534 if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
537 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
538 crd->sys->x509_cred);
541 msg_Err (session, "cannot set TLS session credentials: %s",
542 gnutls_strerror (val));
546 if (session->handshake == gnutls_HandshakeAndValidate)
547 gnutls_certificate_server_set_request (sys->session,
548 GNUTLS_CERT_REQUIRE);
550 gnutls_transport_set_ptr (sys->session,
551 (gnutls_transport_ptr_t)(intptr_t)fd);
555 gnutls_SessionClose (crd, session);
561 * Adds one or more Certificate Authorities to the trusted set.
563 * @param path (UTF-8) path to an X.509 certificates list.
565 * @return -1 on error, 0 on success.
567 static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
569 block_t *block = block_FilePath (path);
572 msg_Err (crd, "cannot read trusted CA from %s: %m", path);
577 .data = block->p_buffer,
578 .size = block->i_buffer,
581 int val = gnutls_certificate_set_x509_trust_mem (crd->sys->x509_cred, &d,
582 GNUTLS_X509_FMT_PEM);
583 block_Release (block);
586 msg_Err (crd, "cannot load trusted CA from %s: %s", path,
587 gnutls_strerror (val));
590 msg_Dbg (crd, " %d trusted CA%s added from %s", val, (val != 1) ? "s" : "",
593 /* enables peer's certificate verification */
594 crd->sys->handshake = gnutls_HandshakeAndValidate;
600 * Adds a Certificates Revocation List to be sent to TLS clients.
602 * @param path (UTF-8) path of the CRL file.
604 * @return -1 on error, 0 on success.
606 static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
608 block_t *block = block_FilePath (path);
611 msg_Err (crd, "cannot read CRL from %s: %m", path);
616 .data = block->p_buffer,
617 .size = block->i_buffer,
620 int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
621 GNUTLS_X509_FMT_PEM);
622 block_Release (block);
625 msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
628 msg_Dbg (crd, "%d CRL%s added from %s", val, (val != 1) ? "s" : "", path);
634 * Allocates a whole server's TLS credentials.
636 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
640 if (gnutls_Init (VLC_OBJECT(crd)))
643 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
644 if (unlikely(sys == NULL))
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;
655 /* Sets server's credentials */
656 val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
659 msg_Err (crd, "cannot allocate credentials: %s",
660 gnutls_strerror (val));
664 block_t *certblock = block_FilePath (cert);
665 if (certblock == NULL)
667 msg_Err (crd, "cannot read certificate chain from %s: %m", cert);
671 block_t *keyblock = block_FilePath (key);
672 if (keyblock == NULL)
674 msg_Err (crd, "cannot read private key from %s: %m", key);
675 block_Release (certblock);
679 gnutls_datum_t pub = {
680 .data = certblock->p_buffer,
681 .size = certblock->i_buffer,
683 .data = keyblock->p_buffer,
684 .size = keyblock->i_buffer,
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);
693 msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
694 gnutls_certificate_free_credentials (sys->x509_cred);
699 * - support other cipher suites
701 val = gnutls_dh_params_init (&sys->dh_params);
704 const gnutls_datum_t data = {
705 .data = (unsigned char *)dh_params,
706 .size = sizeof (dh_params) - 1,
709 val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
710 GNUTLS_X509_FMT_PEM);
712 gnutls_certificate_set_dh_params (sys->x509_cred,
717 msg_Err (crd, "cannot initialize DHE cipher suites: %s",
718 gnutls_strerror (val));
725 gnutls_Deinit (VLC_OBJECT(crd));
730 * Destroys a TLS server object.
732 static void CloseServer (vlc_tls_creds_t *crd)
734 vlc_tls_creds_sys_t *sys = crd->sys;
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);
741 gnutls_Deinit (VLC_OBJECT(crd));