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 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
34 #include <sys/types.h>
36 #ifdef HAVE_SYS_STAT_H
37 # include <sys/stat.h>
41 # include <wincrypt.h>
49 #include <vlc_charset.h>
51 #include <vlc_block.h>
54 #include <gnutls/gnutls.h>
55 #include <gnutls/x509.h>
57 #include <vlc_gcrypt.h>
62 /*****************************************************************************
64 *****************************************************************************/
65 static int OpenClient (vlc_tls_t *, int, const char *);
66 static void CloseClient (vlc_tls_t *);
67 static int OpenServer (vlc_object_t *);
68 static void CloseServer (vlc_object_t *);
70 #define PRIORITIES_TEXT N_("TLS cipher priorities")
71 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
72 "hash functions and compression methods can be selected. " \
73 "Refer to GNU TLS documentation for detailed syntax.")
74 static const char *const priorities_values[] = {
81 static const char *const priorities_text[] = {
82 N_("Performance (prioritize faster ciphers)"),
84 N_("Secure 128-bits (exclude 256-bits ciphers)"),
85 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
86 N_("Export (include insecure ciphers)"),
90 set_shortname( "GNU TLS" )
91 set_description( N_("GNU TLS transport layer security") )
92 set_capability( "tls client", 1 )
93 set_callbacks( OpenClient, CloseClient )
94 set_category( CAT_ADVANCED )
95 set_subcategory( SUBCAT_ADVANCED_MISC )
98 set_description( N_("GNU TLS server") )
99 set_capability( "tls server", 1 )
100 set_category( CAT_ADVANCED )
101 set_subcategory( SUBCAT_ADVANCED_MISC )
102 set_callbacks( OpenServer, CloseServer )
104 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
105 PRIORITIES_LONGTEXT, false)
106 change_string_list (priorities_values, priorities_text, NULL)
109 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
112 * Initializes GnuTLS with proper locking.
113 * @return VLC_SUCCESS on success, a VLC error code otherwise.
115 static int gnutls_Init (vlc_object_t *p_this)
117 int ret = VLC_EGENERIC;
119 vlc_gcrypt_init (); /* GnuTLS depends on gcrypt */
121 vlc_mutex_lock (&gnutls_mutex);
122 if (gnutls_global_init ())
124 msg_Err (p_this, "cannot initialize GnuTLS");
128 const char *psz_version = gnutls_check_version ("2.0.0");
129 if (psz_version == NULL)
131 msg_Err (p_this, "unsupported GnuTLS version");
132 gnutls_global_deinit ();
136 msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
140 vlc_mutex_unlock (&gnutls_mutex);
146 * Deinitializes GnuTLS.
148 static void gnutls_Deinit (vlc_object_t *p_this)
150 vlc_mutex_lock (&gnutls_mutex);
152 gnutls_global_deinit ();
153 msg_Dbg (p_this, "GnuTLS deinitialized");
154 vlc_mutex_unlock (&gnutls_mutex);
158 static int gnutls_Error (vlc_object_t *obj, int val)
164 WSASetLastError (WSAEWOULDBLOCK);
170 case GNUTLS_E_INTERRUPTED:
172 WSASetLastError (WSAEINTR);
179 msg_Err (obj, "%s", gnutls_strerror (val));
181 if (!gnutls_error_is_fatal (val))
182 msg_Err (obj, "Error above should be handled");
185 WSASetLastError (WSAECONNRESET);
192 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
197 gnutls_session_t session;
198 gnutls_certificate_credentials_t x509_cred;
205 * Sends data through a TLS session.
207 static int gnutls_Send (void *opaque, const void *buf, size_t length)
209 vlc_tls_t *session = opaque;
210 vlc_tls_sys_t *sys = session->sys;
212 int val = gnutls_record_send (sys->session, buf, length);
213 return (val < 0) ? gnutls_Error (session, val) : val;
218 * Receives data through a TLS session.
220 static int gnutls_Recv (void *opaque, void *buf, size_t length)
222 vlc_tls_t *session = opaque;
223 vlc_tls_sys_t *sys = session->sys;
225 int val = gnutls_record_recv (sys->session, buf, length);
226 return (val < 0) ? gnutls_Error (session, val) : val;
231 * Starts or continues the TLS handshake.
233 * @return -1 on fatal error, 0 on successful handshake completion,
234 * 1 if more would-be blocking recv is needed,
235 * 2 if more would-be blocking send is required.
237 static int gnutls_ContinueHandshake (vlc_tls_t *session)
239 vlc_tls_sys_t *sys = session->sys;
245 val = gnutls_handshake (sys->session);
246 if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
247 return 1 + gnutls_record_get_direction (sys->session);
252 msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
254 msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
258 sys->handshaked = true;
269 static const error_msg_t cert_errors[] =
271 { GNUTLS_CERT_INVALID,
272 "Certificate could not be verified" },
273 { GNUTLS_CERT_REVOKED,
274 "Certificate was revoked" },
275 { GNUTLS_CERT_SIGNER_NOT_FOUND,
276 "Certificate's signer was not found" },
277 { GNUTLS_CERT_SIGNER_NOT_CA,
278 "Certificate's signer is not a CA" },
279 { GNUTLS_CERT_INSECURE_ALGORITHM,
280 "Insecure certificate signature algorithm" },
285 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
287 vlc_tls_sys_t *sys = session->sys;
289 int val = gnutls_ContinueHandshake (session);
293 /* certificates chain verification */
296 val = gnutls_certificate_verify_peers2 (sys->session, &status);
299 msg_Err (session, "Certificate verification failed: %s",
300 gnutls_strerror (val));
306 msg_Err (session, "TLS session: access denied");
307 for (const error_msg_t *e = cert_errors; e->flag; e++)
309 if (status & e->flag)
311 msg_Err (session, "%s", e->msg);
318 "unknown certificate error (you found a bug in VLC)");
322 /* certificate (host)name verification */
323 const gnutls_datum_t *data;
324 data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
327 msg_Err (session, "Peer certificate not available");
331 gnutls_x509_crt_t cert;
332 val = gnutls_x509_crt_init (&cert);
335 msg_Err (session, "x509 fatal error: %s", gnutls_strerror (val));
339 val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
342 msg_Err (session, "Certificate import error: %s",
343 gnutls_strerror (val));
347 if (sys->hostname != NULL
348 && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
350 msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
357 if (gnutls_x509_crt_get_expiration_time (cert) < now)
359 msg_Err (session, "Certificate expired");
363 if (gnutls_x509_crt_get_activation_time (cert) > now)
365 msg_Err( session, "Certificate not yet valid" );
369 gnutls_x509_crt_deinit (cert);
370 msg_Dbg (session, "TLS/x509 certificate verified");
374 gnutls_x509_crt_deinit (cert);
379 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
381 char *priorities = var_InheritString (obj, "gnutls-priorities");
382 if (unlikely(priorities == NULL))
386 int val = gnutls_priority_set_direct (session, priorities, &errp);
389 msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
390 gnutls_strerror (val));
401 gnutls_Addx509File( vlc_object_t *p_this,
402 gnutls_certificate_credentials_t cred,
403 const char *psz_path, bool b_priv );
405 static int gnutls_loadOSCAList(vlc_object_t *p_this,
406 gnutls_certificate_credentials_t cred);
410 gnutls_Addx509Directory( vlc_object_t *p_this,
411 gnutls_certificate_credentials_t cred,
412 const char *psz_dirname,
417 if( *psz_dirname == '\0' )
420 dir = vlc_opendir( psz_dirname );
425 msg_Err (p_this, "cannot open directory (%s): %m", psz_dirname);
429 msg_Dbg (p_this, "creating empty certificate directory: %s",
431 vlc_mkdir (psz_dirname, b_priv ? 0700 : 0755);
437 struct stat st1, st2;
438 int fd = dirfd( dir );
441 * Gets stats for the directory path, checks that it is not a
442 * symbolic link (to avoid possibly infinite recursion), and verifies
443 * that the inode is still the same, to avoid TOCTOU race condition.
446 || fstat( fd, &st1 ) || vlc_lstat( psz_dirname, &st2 )
447 || S_ISLNK( st2.st_mode ) || ( st1.st_ino != st2.st_ino ) )
457 char *ent = vlc_readdir (dir);
461 if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
467 char path[strlen (psz_dirname) + strlen (ent) + 2];
468 sprintf (path, "%s"DIR_SEP"%s", psz_dirname, ent);
471 gnutls_Addx509File( p_this, cred, path, b_priv );
480 gnutls_Addx509File( vlc_object_t *p_this,
481 gnutls_certificate_credentials cred,
482 const char *psz_path, bool b_priv )
486 int fd = vlc_open (psz_path, O_RDONLY);
490 block_t *block = block_File (fd);
495 gnutls_datum data = {
496 .data = block->p_buffer,
497 .size = block->i_buffer,
500 ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
502 : gnutls_certificate_set_x509_trust_mem (cred, &data,
503 GNUTLS_X509_FMT_PEM);
504 block_Release (block);
508 msg_Warn (p_this, "cannot add x509 credentials (%s): %s",
509 psz_path, gnutls_strerror (res));
512 msg_Dbg (p_this, "added %d %s(s) from %s", res,
513 b_priv ? "key" : "certificate", psz_path);
517 if (!fstat (fd, &st) && S_ISDIR (st.st_mode))
520 msg_Dbg (p_this, "looking recursively for x509 credentials in %s",
522 return gnutls_Addx509Directory (p_this, cred, psz_path, b_priv);
526 msg_Warn (p_this, "cannot add x509 credentials (%s): %m", psz_path);
534 gnutls_loadOSCAList (vlc_object_t *p_this,
535 gnutls_certificate_credentials cred)
537 HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
540 msg_Warn (p_this, "could not open the Cert SystemStore");
544 PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
545 while( pCertContext )
547 gnutls_datum data = {
548 .data = pCertContext->pbCertEncoded,
549 .size = pCertContext->cbCertEncoded,
552 if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
554 msg_Warn (p_this, "cannot add x509 credential");
558 pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
565 * Initializes a client-side TLS session.
567 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
569 if (gnutls_Init (VLC_OBJECT(session)))
572 vlc_tls_sys_t *sys = malloc (sizeof (*sys));
573 if (unlikely(sys == NULL))
575 gnutls_Deinit (VLC_OBJECT(session));
580 session->sock.p_sys = session;
581 session->sock.pf_send = gnutls_Send;
582 session->sock.pf_recv = gnutls_Recv;
583 sys->handshaked = false;
585 int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
588 msg_Err (session, "cannot allocate X509 credentials: %s",
589 gnutls_strerror (val));
593 char *userdir = config_GetUserDir (VLC_DATA_DIR);
596 char path[strlen (userdir) + sizeof ("/ssl/private")];
597 sprintf (path, "%s/ssl", userdir);
598 vlc_mkdir (path, 0755);
600 sprintf (path, "%s/ssl/certs", userdir);
601 gnutls_Addx509Directory (VLC_OBJECT(session), sys->x509_cred, path, false);
602 sprintf (path, "%s/ssl/private", userdir);
603 gnutls_Addx509Directory (VLC_OBJECT(session), sys->x509_cred, path, true);
608 gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
610 const char *confdir = config_GetConfDir ();
612 char path[strlen (confdir)
613 + sizeof ("/ssl/certs/ca-certificates.crt")];
614 sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
615 gnutls_Addx509File (VLC_OBJECT(session), sys->x509_cred, path, false);
618 session->handshake = gnutls_HandshakeAndValidate;
619 /*session->_handshake = gnutls_ContinueHandshake;*/
621 val = gnutls_init (&sys->session, GNUTLS_CLIENT);
624 msg_Err (session, "cannot initialize TLS session: %s",
625 gnutls_strerror (val));
626 gnutls_certificate_free_credentials (sys->x509_cred);
630 if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
633 /* minimum DH prime bits */
634 gnutls_dh_set_prime_bits (sys->session, 1024);
636 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
640 msg_Err (session, "cannot set TLS session credentials: %s",
641 gnutls_strerror (val));
646 if (likely(hostname != NULL))
648 /* fill Server Name Indication */
649 gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
650 hostname, strlen (hostname));
651 /* keep hostname to match CNAME after handshake */
652 sys->hostname = strdup (hostname);
653 if (unlikely(sys->hostname == NULL))
657 sys->hostname = NULL;
659 gnutls_transport_set_ptr (sys->session,
660 (gnutls_transport_ptr_t)(intptr_t)fd);
664 gnutls_deinit (sys->session);
665 gnutls_certificate_free_credentials (sys->x509_cred);
667 gnutls_Deinit (VLC_OBJECT(session));
673 static void CloseClient (vlc_tls_t *session)
675 vlc_tls_sys_t *sys = session->sys;
678 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
679 gnutls_deinit (sys->session);
680 /* credentials must be free'd *after* gnutls_deinit() */
681 gnutls_certificate_free_credentials (sys->x509_cred);
683 gnutls_Deinit (VLC_OBJECT(session));
684 free (sys->hostname);
692 struct vlc_tls_creds_sys
694 gnutls_certificate_credentials_t x509_cred;
695 gnutls_dh_params_t dh_params;
696 int (*handshake) (vlc_tls_t *);
701 * Terminates TLS session and releases session data.
702 * You still have to close the socket yourself.
704 static void gnutls_SessionClose (vlc_tls_t *session)
706 vlc_tls_sys_t *sys = session->sys;
709 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
710 gnutls_deinit (sys->session);
712 vlc_object_release (session);
718 * Initializes a server-side TLS session.
720 static vlc_tls_t *gnutls_SessionOpen (vlc_tls_creds_t *server, int fd)
722 vlc_tls_creds_sys_t *ssys = server->sys;
725 vlc_tls_t *session = vlc_object_create (server, sizeof (*session));
726 if (unlikely(session == NULL))
729 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
730 if (unlikely(sys == NULL))
732 vlc_object_release (session);
737 session->sock.p_sys = session;
738 session->sock.pf_send = gnutls_Send;
739 session->sock.pf_recv = gnutls_Recv;
740 session->handshake = ssys->handshake;
741 session->u.close = gnutls_SessionClose;
742 sys->handshaked = false;
743 sys->hostname = NULL;
745 val = gnutls_init (&sys->session, GNUTLS_SERVER);
748 msg_Err (server, "cannot initialize TLS session: %s",
749 gnutls_strerror (val));
751 vlc_object_release (session);
755 if (gnutls_SessionPrioritize (VLC_OBJECT (server), sys->session))
758 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
762 msg_Err (server, "cannot set TLS session credentials: %s",
763 gnutls_strerror (val));
767 if (session->handshake == gnutls_HandshakeAndValidate)
768 gnutls_certificate_server_set_request (sys->session,
769 GNUTLS_CERT_REQUIRE);
771 gnutls_transport_set_ptr (sys->session,
772 (gnutls_transport_ptr_t)(intptr_t)fd);
776 gnutls_SessionClose (session);
782 * Adds one or more certificate authorities.
784 * @param ca_path (Unicode) path to an x509 certificates list.
786 * @return -1 on error, 0 on success.
788 static int gnutls_ServerAddCA (vlc_tls_creds_t *server, const char *ca_path)
790 vlc_tls_creds_sys_t *sys = server->sys;
791 char *local_path = ToLocale (ca_path);
793 int val = gnutls_certificate_set_x509_trust_file (sys->x509_cred,
795 GNUTLS_X509_FMT_PEM );
796 LocaleFree (local_path);
799 msg_Err (server, "cannot add trusted CA (%s): %s", ca_path,
800 gnutls_strerror (val));
803 msg_Dbg (server, " %d trusted CA added (%s)", val, ca_path);
805 /* enables peer's certificate verification */
806 sys->handshake = gnutls_HandshakeAndValidate;
813 * Adds a certificates revocation list to be sent to TLS clients.
815 * @param crl_path (Unicode) path of the CRL file.
817 * @return -1 on error, 0 on success.
819 static int gnutls_ServerAddCRL (vlc_tls_creds_t *server, const char *crl_path)
821 vlc_tls_creds_sys_t *sys = server->sys;
822 char *local_path = ToLocale (crl_path);
824 int val = gnutls_certificate_set_x509_crl_file (sys->x509_cred,
826 GNUTLS_X509_FMT_PEM);
827 LocaleFree (local_path);
830 msg_Err (server, "cannot add CRL (%s): %s", crl_path,
831 gnutls_strerror (val));
834 msg_Dbg (server, "%d CRL added (%s)", val, crl_path);
840 * Allocates a whole server's TLS credentials.
842 static int OpenServer (vlc_object_t *obj)
844 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
847 if (gnutls_Init (obj))
850 msg_Dbg (obj, "creating TLS server");
852 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
853 if (unlikely(sys == NULL))
857 server->add_CA = gnutls_ServerAddCA;
858 server->add_CRL = gnutls_ServerAddCRL;
859 server->open = gnutls_SessionOpen;
860 /* No certificate validation by default */
861 sys->handshake = gnutls_ContinueHandshake;
863 /* Sets server's credentials */
864 val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
867 msg_Err (server, "cannot allocate X509 credentials: %s",
868 gnutls_strerror (val));
872 char *cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
873 char *key_path = var_GetNonEmptyString (obj, "tls-x509-key");
874 const char *lcert = ToLocale (cert_path);
875 const char *lkey = ToLocale (key_path);
876 val = gnutls_certificate_set_x509_key_file (sys->x509_cred, lcert, lkey,
877 GNUTLS_X509_FMT_PEM);
885 msg_Err (server, "cannot set certificate chain or private key: %s",
886 gnutls_strerror (val));
887 gnutls_certificate_free_credentials (sys->x509_cred);
892 * - support other cipher suites
894 val = gnutls_dh_params_init (&sys->dh_params);
897 const gnutls_datum_t data = {
898 .data = (unsigned char *)dh_params,
899 .size = sizeof (dh_params) - 1,
902 val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
903 GNUTLS_X509_FMT_PEM);
905 gnutls_certificate_set_dh_params (sys->x509_cred,
910 msg_Err (server, "cannot initialize DHE cipher suites: %s",
911 gnutls_strerror (val));
922 * Destroys a TLS server object.
924 static void CloseServer (vlc_object_t *obj)
926 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
927 vlc_tls_creds_sys_t *sys = server->sys;
929 /* all sessions depending on the server are now deinitialized */
930 gnutls_certificate_free_credentials (sys->x509_cred);
931 gnutls_dh_params_deinit (sys->dh_params);