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 * Loads x509 credentials from a file descriptor (directory or regular file)
402 * and closes the descriptor.
404 static void gnutls_Addx509FD (vlc_object_t *obj,
405 gnutls_certificate_credentials_t cred,
406 int fd, bool priv, unsigned recursion)
408 DIR *dir = fdopendir (fd);
417 char *ent = vlc_readdir (dir);
421 if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
427 int nfd = vlc_openat (fd, ent, O_RDONLY);
430 msg_Dbg (obj, "loading x509 credentials from %s...", ent);
431 gnutls_Addx509FD (obj, cred, nfd, priv, recursion);
434 msg_Dbg (obj, "cannot access x509 credentials in %s", ent);
442 block_t *block = block_File (fd);
445 gnutls_datum data = {
446 .data = block->p_buffer,
447 .size = block->i_buffer,
450 ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
452 : gnutls_certificate_set_x509_trust_mem (cred, &data,
453 GNUTLS_X509_FMT_PEM);
454 block_Release (block);
457 msg_Warn (obj, "cannot add x509 credentials: %s",
458 gnutls_strerror (res));
460 msg_Dbg (obj, "added %d %s(s)", res, priv ? "key" : "certificate");
463 msg_Warn (obj, "cannot read x509 credentials: %m");
467 static void gnutls_Addx509Directory (vlc_object_t *obj,
468 gnutls_certificate_credentials cred,
469 const char *path, bool priv)
471 msg_Dbg (obj, "browsing x509 credentials in %s...", path);
472 int fd = vlc_open (path, O_RDONLY|O_DIRECTORY);
475 msg_Warn (obj, "cannot access x509 in %s: %m", path);
479 gnutls_Addx509FD (obj, cred, fd, priv, 5);
482 static void gnutls_Addx509File (vlc_object_t *obj,
483 gnutls_certificate_credentials cred,
484 const char *path, bool priv)
486 msg_Dbg (obj, "loading x509 credentials from %s...", path);
488 int fd = vlc_open (path, O_RDONLY);
491 msg_Warn (obj, "cannot access x509 in %s: %m", path);
495 gnutls_Addx509FD (obj, cred, fd, priv, 0);
499 gnutls_loadOSCAList (vlc_object_t *p_this,
500 gnutls_certificate_credentials cred)
502 HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
505 msg_Warn (p_this, "could not open the Cert SystemStore");
509 PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
510 while( pCertContext )
512 gnutls_datum data = {
513 .data = pCertContext->pbCertEncoded,
514 .size = pCertContext->cbCertEncoded,
517 if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
519 msg_Warn (p_this, "cannot add x509 credential");
523 pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
530 * Initializes a client-side TLS session.
532 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
534 if (gnutls_Init (VLC_OBJECT(session)))
537 vlc_tls_sys_t *sys = malloc (sizeof (*sys));
538 if (unlikely(sys == NULL))
540 gnutls_Deinit (VLC_OBJECT(session));
545 session->sock.p_sys = session;
546 session->sock.pf_send = gnutls_Send;
547 session->sock.pf_recv = gnutls_Recv;
548 sys->handshaked = false;
550 int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
553 msg_Err (session, "cannot allocate X509 credentials: %s",
554 gnutls_strerror (val));
559 char *userdir = config_GetUserDir (VLC_DATA_DIR);
562 char path[strlen (userdir) + sizeof ("/ssl/private")];
563 sprintf (path, "%s/ssl", userdir);
564 vlc_mkdir (path, 0755);
566 sprintf (path, "%s/ssl/certs", userdir);
567 gnutls_Addx509Directory (VLC_OBJECT(session), sys->x509_cred, path, false);
568 sprintf (path, "%s/ssl/private", userdir);
569 gnutls_Addx509Directory (VLC_OBJECT(session), sys->x509_cred, path, true);
573 const char *confdir = config_GetConfDir ();
575 char path[strlen (confdir)
576 + sizeof ("/ssl/certs/ca-certificates.crt")];
577 sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
578 gnutls_Addx509File (VLC_OBJECT(session), sys->x509_cred, path, false);
581 gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
583 session->handshake = gnutls_HandshakeAndValidate;
584 /*session->_handshake = gnutls_ContinueHandshake;*/
586 val = gnutls_init (&sys->session, GNUTLS_CLIENT);
589 msg_Err (session, "cannot initialize TLS session: %s",
590 gnutls_strerror (val));
591 gnutls_certificate_free_credentials (sys->x509_cred);
595 if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
598 /* minimum DH prime bits */
599 gnutls_dh_set_prime_bits (sys->session, 1024);
601 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
605 msg_Err (session, "cannot set TLS session credentials: %s",
606 gnutls_strerror (val));
611 if (likely(hostname != NULL))
613 /* fill Server Name Indication */
614 gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
615 hostname, strlen (hostname));
616 /* keep hostname to match CNAME after handshake */
617 sys->hostname = strdup (hostname);
618 if (unlikely(sys->hostname == NULL))
622 sys->hostname = NULL;
624 gnutls_transport_set_ptr (sys->session,
625 (gnutls_transport_ptr_t)(intptr_t)fd);
629 gnutls_deinit (sys->session);
630 gnutls_certificate_free_credentials (sys->x509_cred);
632 gnutls_Deinit (VLC_OBJECT(session));
638 static void CloseClient (vlc_tls_t *session)
640 vlc_tls_sys_t *sys = session->sys;
643 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
644 gnutls_deinit (sys->session);
645 /* credentials must be free'd *after* gnutls_deinit() */
646 gnutls_certificate_free_credentials (sys->x509_cred);
648 gnutls_Deinit (VLC_OBJECT(session));
649 free (sys->hostname);
657 struct vlc_tls_creds_sys
659 gnutls_certificate_credentials_t x509_cred;
660 gnutls_dh_params_t dh_params;
661 int (*handshake) (vlc_tls_t *);
666 * Terminates TLS session and releases session data.
667 * You still have to close the socket yourself.
669 static void gnutls_SessionClose (vlc_tls_t *session)
671 vlc_tls_sys_t *sys = session->sys;
674 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
675 gnutls_deinit (sys->session);
677 vlc_object_release (session);
683 * Initializes a server-side TLS session.
685 static vlc_tls_t *gnutls_SessionOpen (vlc_tls_creds_t *server, int fd)
687 vlc_tls_creds_sys_t *ssys = server->sys;
690 vlc_tls_t *session = vlc_object_create (server, sizeof (*session));
691 if (unlikely(session == NULL))
694 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
695 if (unlikely(sys == NULL))
697 vlc_object_release (session);
702 session->sock.p_sys = session;
703 session->sock.pf_send = gnutls_Send;
704 session->sock.pf_recv = gnutls_Recv;
705 session->handshake = ssys->handshake;
706 session->u.close = gnutls_SessionClose;
707 sys->handshaked = false;
708 sys->hostname = NULL;
710 val = gnutls_init (&sys->session, GNUTLS_SERVER);
713 msg_Err (server, "cannot initialize TLS session: %s",
714 gnutls_strerror (val));
716 vlc_object_release (session);
720 if (gnutls_SessionPrioritize (VLC_OBJECT (server), sys->session))
723 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
727 msg_Err (server, "cannot set TLS session credentials: %s",
728 gnutls_strerror (val));
732 if (session->handshake == gnutls_HandshakeAndValidate)
733 gnutls_certificate_server_set_request (sys->session,
734 GNUTLS_CERT_REQUIRE);
736 gnutls_transport_set_ptr (sys->session,
737 (gnutls_transport_ptr_t)(intptr_t)fd);
741 gnutls_SessionClose (session);
747 * Adds one or more certificate authorities.
749 * @param ca_path (Unicode) path to an x509 certificates list.
751 * @return -1 on error, 0 on success.
753 static int gnutls_ServerAddCA (vlc_tls_creds_t *server, const char *ca_path)
755 vlc_tls_creds_sys_t *sys = server->sys;
756 char *local_path = ToLocale (ca_path);
758 int val = gnutls_certificate_set_x509_trust_file (sys->x509_cred,
760 GNUTLS_X509_FMT_PEM );
761 LocaleFree (local_path);
764 msg_Err (server, "cannot add trusted CA (%s): %s", ca_path,
765 gnutls_strerror (val));
768 msg_Dbg (server, " %d trusted CA added (%s)", val, ca_path);
770 /* enables peer's certificate verification */
771 sys->handshake = gnutls_HandshakeAndValidate;
778 * Adds a certificates revocation list to be sent to TLS clients.
780 * @param crl_path (Unicode) path of the CRL file.
782 * @return -1 on error, 0 on success.
784 static int gnutls_ServerAddCRL (vlc_tls_creds_t *server, const char *crl_path)
786 vlc_tls_creds_sys_t *sys = server->sys;
787 char *local_path = ToLocale (crl_path);
789 int val = gnutls_certificate_set_x509_crl_file (sys->x509_cred,
791 GNUTLS_X509_FMT_PEM);
792 LocaleFree (local_path);
795 msg_Err (server, "cannot add CRL (%s): %s", crl_path,
796 gnutls_strerror (val));
799 msg_Dbg (server, "%d CRL added (%s)", val, crl_path);
805 * Allocates a whole server's TLS credentials.
807 static int OpenServer (vlc_object_t *obj)
809 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
812 if (gnutls_Init (obj))
815 msg_Dbg (obj, "creating TLS server");
817 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
818 if (unlikely(sys == NULL))
822 server->add_CA = gnutls_ServerAddCA;
823 server->add_CRL = gnutls_ServerAddCRL;
824 server->open = gnutls_SessionOpen;
825 /* No certificate validation by default */
826 sys->handshake = gnutls_ContinueHandshake;
828 /* Sets server's credentials */
829 val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
832 msg_Err (server, "cannot allocate X509 credentials: %s",
833 gnutls_strerror (val));
837 char *cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
838 char *key_path = var_GetNonEmptyString (obj, "tls-x509-key");
839 const char *lcert = ToLocale (cert_path);
840 const char *lkey = ToLocale (key_path);
841 val = gnutls_certificate_set_x509_key_file (sys->x509_cred, lcert, lkey,
842 GNUTLS_X509_FMT_PEM);
850 msg_Err (server, "cannot set certificate chain or private key: %s",
851 gnutls_strerror (val));
852 gnutls_certificate_free_credentials (sys->x509_cred);
857 * - support other cipher suites
859 val = gnutls_dh_params_init (&sys->dh_params);
862 const gnutls_datum_t data = {
863 .data = (unsigned char *)dh_params,
864 .size = sizeof (dh_params) - 1,
867 val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
868 GNUTLS_X509_FMT_PEM);
870 gnutls_certificate_set_dh_params (sys->x509_cred,
875 msg_Err (server, "cannot initialize DHE cipher suites: %s",
876 gnutls_strerror (val));
887 * Destroys a TLS server object.
889 static void CloseServer (vlc_object_t *obj)
891 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
892 vlc_tls_creds_sys_t *sys = server->sys;
894 /* all sessions depending on the server are now deinitialized */
895 gnutls_certificate_free_credentials (sys->x509_cred);
896 gnutls_dh_params_deinit (sys->dh_params);