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 *****************************************************************************/
31 #include <sys/types.h>
38 # include <wincrypt.h>
44 #include <vlc_common.h>
45 #include <vlc_plugin.h>
47 #include <vlc_charset.h>
49 #include <vlc_block.h>
52 #include <gnutls/gnutls.h>
53 #include <gnutls/x509.h>
55 #include <vlc_gcrypt.h>
60 /*****************************************************************************
62 *****************************************************************************/
63 static int OpenClient (vlc_tls_t *, int, const char *);
64 static void CloseClient (vlc_tls_t *);
65 static int OpenServer (vlc_object_t *);
66 static void CloseServer (vlc_object_t *);
68 #define PRIORITIES_TEXT N_("TLS cipher priorities")
69 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
70 "hash functions and compression methods can be selected. " \
71 "Refer to GNU TLS documentation for detailed syntax.")
72 static const char *const priorities_values[] = {
79 static const char *const priorities_text[] = {
80 N_("Performance (prioritize faster ciphers)"),
82 N_("Secure 128-bits (exclude 256-bits ciphers)"),
83 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
84 N_("Export (include insecure ciphers)"),
88 set_shortname( "GNU TLS" )
89 set_description( N_("GNU TLS transport layer security") )
90 set_capability( "tls client", 1 )
91 set_callbacks( OpenClient, CloseClient )
92 set_category( CAT_ADVANCED )
93 set_subcategory( SUBCAT_ADVANCED_MISC )
96 set_description( N_("GNU TLS server") )
97 set_capability( "tls server", 1 )
98 set_category( CAT_ADVANCED )
99 set_subcategory( SUBCAT_ADVANCED_MISC )
100 set_callbacks( OpenServer, CloseServer )
102 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
103 PRIORITIES_LONGTEXT, false)
104 change_string_list (priorities_values, priorities_text, NULL)
107 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
110 * Initializes GnuTLS with proper locking.
111 * @return VLC_SUCCESS on success, a VLC error code otherwise.
113 static int gnutls_Init (vlc_object_t *p_this)
115 int ret = VLC_EGENERIC;
117 vlc_gcrypt_init (); /* GnuTLS depends on gcrypt */
119 vlc_mutex_lock (&gnutls_mutex);
120 if (gnutls_global_init ())
122 msg_Err (p_this, "cannot initialize GnuTLS");
126 const char *psz_version = gnutls_check_version ("2.0.0");
127 if (psz_version == NULL)
129 msg_Err (p_this, "unsupported GnuTLS version");
130 gnutls_global_deinit ();
134 msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
138 vlc_mutex_unlock (&gnutls_mutex);
144 * Deinitializes GnuTLS.
146 static void gnutls_Deinit (vlc_object_t *p_this)
148 vlc_mutex_lock (&gnutls_mutex);
150 gnutls_global_deinit ();
151 msg_Dbg (p_this, "GnuTLS deinitialized");
152 vlc_mutex_unlock (&gnutls_mutex);
156 static int gnutls_Error (vlc_object_t *obj, int val)
162 WSASetLastError (WSAEWOULDBLOCK);
168 case GNUTLS_E_INTERRUPTED:
170 WSASetLastError (WSAEINTR);
177 msg_Err (obj, "%s", gnutls_strerror (val));
179 if (!gnutls_error_is_fatal (val))
180 msg_Err (obj, "Error above should be handled");
183 WSASetLastError (WSAECONNRESET);
190 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
195 gnutls_session_t session;
196 gnutls_certificate_credentials_t x509_cred;
203 * Sends data through a TLS session.
205 static int gnutls_Send (void *opaque, const void *buf, size_t length)
207 vlc_tls_t *session = opaque;
208 vlc_tls_sys_t *sys = session->sys;
210 int val = gnutls_record_send (sys->session, buf, length);
211 return (val < 0) ? gnutls_Error (session, val) : val;
216 * Receives data through a TLS session.
218 static int gnutls_Recv (void *opaque, void *buf, size_t length)
220 vlc_tls_t *session = opaque;
221 vlc_tls_sys_t *sys = session->sys;
223 int val = gnutls_record_recv (sys->session, buf, length);
224 return (val < 0) ? gnutls_Error (session, val) : val;
229 * Starts or continues the TLS handshake.
231 * @return -1 on fatal error, 0 on successful handshake completion,
232 * 1 if more would-be blocking recv is needed,
233 * 2 if more would-be blocking send is required.
235 static int gnutls_ContinueHandshake (vlc_tls_t *session)
237 vlc_tls_sys_t *sys = session->sys;
243 val = gnutls_handshake (sys->session);
244 if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
245 return 1 + gnutls_record_get_direction (sys->session);
250 msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
252 msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
256 sys->handshaked = true;
267 static const error_msg_t cert_errors[] =
269 { GNUTLS_CERT_INVALID,
270 "Certificate could not be verified" },
271 { GNUTLS_CERT_REVOKED,
272 "Certificate was revoked" },
273 { GNUTLS_CERT_SIGNER_NOT_FOUND,
274 "Certificate's signer was not found" },
275 { GNUTLS_CERT_SIGNER_NOT_CA,
276 "Certificate's signer is not a CA" },
277 { GNUTLS_CERT_INSECURE_ALGORITHM,
278 "Insecure certificate signature algorithm" },
283 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
285 vlc_tls_sys_t *sys = session->sys;
287 int val = gnutls_ContinueHandshake (session);
291 /* certificates chain verification */
294 val = gnutls_certificate_verify_peers2 (sys->session, &status);
297 msg_Err (session, "Certificate verification failed: %s",
298 gnutls_strerror (val));
304 msg_Err (session, "TLS session: access denied");
305 for (const error_msg_t *e = cert_errors; e->flag; e++)
307 if (status & e->flag)
309 msg_Err (session, "%s", e->msg);
316 "unknown certificate error (you found a bug in VLC)");
320 /* certificate (host)name verification */
321 const gnutls_datum_t *data;
322 data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
325 msg_Err (session, "Peer certificate not available");
329 gnutls_x509_crt_t cert;
330 val = gnutls_x509_crt_init (&cert);
333 msg_Err (session, "x509 fatal error: %s", gnutls_strerror (val));
337 val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
340 msg_Err (session, "Certificate import error: %s",
341 gnutls_strerror (val));
345 if (sys->hostname != NULL
346 && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
348 msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
355 if (gnutls_x509_crt_get_expiration_time (cert) < now)
357 msg_Err (session, "Certificate expired");
361 if (gnutls_x509_crt_get_activation_time (cert) > now)
363 msg_Err( session, "Certificate not yet valid" );
367 gnutls_x509_crt_deinit (cert);
368 msg_Dbg (session, "TLS/x509 certificate verified");
372 gnutls_x509_crt_deinit (cert);
377 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
379 char *priorities = var_InheritString (obj, "gnutls-priorities");
380 if (unlikely(priorities == NULL))
384 int val = gnutls_priority_set_direct (session, priorities, &errp);
387 msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
388 gnutls_strerror (val));
399 * Loads x509 credentials from a file descriptor (directory or regular file)
400 * and closes the descriptor.
402 static void gnutls_x509_AddFD (vlc_object_t *obj,
403 gnutls_certificate_credentials_t cred,
404 int fd, bool priv, unsigned recursion)
406 DIR *dir = fdopendir (fd);
415 char *ent = vlc_readdir (dir);
419 if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
425 int nfd = vlc_openat (fd, ent, O_RDONLY);
428 msg_Dbg (obj, "loading x509 credentials from %s...", ent);
429 gnutls_x509_AddFD (obj, cred, nfd, priv, recursion);
432 msg_Dbg (obj, "cannot access x509 credentials in %s", ent);
440 block_t *block = block_File (fd);
443 gnutls_datum data = {
444 .data = block->p_buffer,
445 .size = block->i_buffer,
448 ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
450 : gnutls_certificate_set_x509_trust_mem (cred, &data,
451 GNUTLS_X509_FMT_PEM);
452 block_Release (block);
455 msg_Warn (obj, "cannot add x509 credentials: %s",
456 gnutls_strerror (res));
458 msg_Dbg (obj, "added %d %s(s)", res, priv ? "key" : "certificate");
461 msg_Warn (obj, "cannot read x509 credentials: %m");
465 static void gnutls_x509_AddPath (vlc_object_t *obj,
466 gnutls_certificate_credentials cred,
467 const char *path, bool priv)
469 msg_Dbg (obj, "loading x509 credentials in %s...", path);
470 int fd = vlc_open (path, O_RDONLY);
473 msg_Warn (obj, "cannot access x509 in %s: %m", path);
477 gnutls_x509_AddFD (obj, cred, fd, priv, 5);
481 gnutls_loadOSCAList (vlc_object_t *p_this,
482 gnutls_certificate_credentials cred)
484 HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
487 msg_Warn (p_this, "could not open the Cert SystemStore");
491 PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
492 while( pCertContext )
494 gnutls_datum data = {
495 .data = pCertContext->pbCertEncoded,
496 .size = pCertContext->cbCertEncoded,
499 if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
501 msg_Warn (p_this, "cannot add x509 credential");
505 pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
512 * Initializes a client-side TLS session.
514 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
516 if (gnutls_Init (VLC_OBJECT(session)))
519 vlc_tls_sys_t *sys = malloc (sizeof (*sys));
520 if (unlikely(sys == NULL))
522 gnutls_Deinit (VLC_OBJECT(session));
527 session->sock.p_sys = session;
528 session->sock.pf_send = gnutls_Send;
529 session->sock.pf_recv = gnutls_Recv;
530 sys->handshaked = false;
532 int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
535 msg_Err (session, "cannot allocate X509 credentials: %s",
536 gnutls_strerror (val));
541 char *userdir = config_GetUserDir (VLC_DATA_DIR);
544 char path[strlen (userdir) + sizeof ("/ssl/private/")];
545 sprintf (path, "%s/ssl", userdir);
546 vlc_mkdir (path, 0755);
548 sprintf (path, "%s/ssl/certs/", userdir);
549 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
550 sprintf (path, "%s/ssl/private/", userdir);
551 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, true);
555 const char *confdir = config_GetConfDir ();
557 char path[strlen (confdir)
558 + sizeof ("/ssl/certs/ca-certificates.crt")];
559 sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
560 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
563 gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
565 gnutls_certificate_set_verify_flags (sys->x509_cred,
566 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
568 session->handshake = gnutls_HandshakeAndValidate;
569 /*session->_handshake = gnutls_ContinueHandshake;*/
571 val = gnutls_init (&sys->session, GNUTLS_CLIENT);
574 msg_Err (session, "cannot initialize TLS session: %s",
575 gnutls_strerror (val));
576 gnutls_certificate_free_credentials (sys->x509_cred);
580 if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
583 /* minimum DH prime bits */
584 gnutls_dh_set_prime_bits (sys->session, 1024);
586 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
590 msg_Err (session, "cannot set TLS session credentials: %s",
591 gnutls_strerror (val));
596 if (likely(hostname != NULL))
598 /* fill Server Name Indication */
599 gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
600 hostname, strlen (hostname));
601 /* keep hostname to match CNAME after handshake */
602 sys->hostname = strdup (hostname);
603 if (unlikely(sys->hostname == NULL))
607 sys->hostname = NULL;
609 gnutls_transport_set_ptr (sys->session,
610 (gnutls_transport_ptr_t)(intptr_t)fd);
614 gnutls_deinit (sys->session);
615 gnutls_certificate_free_credentials (sys->x509_cred);
617 gnutls_Deinit (VLC_OBJECT(session));
623 static void CloseClient (vlc_tls_t *session)
625 vlc_tls_sys_t *sys = session->sys;
628 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
629 gnutls_deinit (sys->session);
630 /* credentials must be free'd *after* gnutls_deinit() */
631 gnutls_certificate_free_credentials (sys->x509_cred);
633 gnutls_Deinit (VLC_OBJECT(session));
634 free (sys->hostname);
642 struct vlc_tls_creds_sys
644 gnutls_certificate_credentials_t x509_cred;
645 gnutls_dh_params_t dh_params;
646 int (*handshake) (vlc_tls_t *);
651 * Terminates TLS session and releases session data.
652 * You still have to close the socket yourself.
654 static void gnutls_SessionClose (vlc_tls_t *session)
656 vlc_tls_sys_t *sys = session->sys;
659 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
660 gnutls_deinit (sys->session);
662 vlc_object_release (session);
668 * Initializes a server-side TLS session.
670 static vlc_tls_t *gnutls_SessionOpen (vlc_tls_creds_t *server, int fd)
672 vlc_tls_creds_sys_t *ssys = server->sys;
675 vlc_tls_t *session = vlc_object_create (server, sizeof (*session));
676 if (unlikely(session == NULL))
679 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
680 if (unlikely(sys == NULL))
682 vlc_object_release (session);
687 session->sock.p_sys = session;
688 session->sock.pf_send = gnutls_Send;
689 session->sock.pf_recv = gnutls_Recv;
690 session->handshake = ssys->handshake;
691 session->u.close = gnutls_SessionClose;
692 sys->handshaked = false;
693 sys->hostname = NULL;
695 val = gnutls_init (&sys->session, GNUTLS_SERVER);
698 msg_Err (server, "cannot initialize TLS session: %s",
699 gnutls_strerror (val));
701 vlc_object_release (session);
705 if (gnutls_SessionPrioritize (VLC_OBJECT (server), sys->session))
708 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
712 msg_Err (server, "cannot set TLS session credentials: %s",
713 gnutls_strerror (val));
717 if (session->handshake == gnutls_HandshakeAndValidate)
718 gnutls_certificate_server_set_request (sys->session,
719 GNUTLS_CERT_REQUIRE);
721 gnutls_transport_set_ptr (sys->session,
722 (gnutls_transport_ptr_t)(intptr_t)fd);
726 gnutls_SessionClose (session);
732 * Adds one or more certificate authorities.
734 * @param ca_path (Unicode) path to an x509 certificates list.
736 * @return -1 on error, 0 on success.
738 static int gnutls_ServerAddCA (vlc_tls_creds_t *server, const char *ca_path)
740 vlc_tls_creds_sys_t *sys = server->sys;
741 char *local_path = ToLocale (ca_path);
743 int val = gnutls_certificate_set_x509_trust_file (sys->x509_cred,
745 GNUTLS_X509_FMT_PEM );
746 LocaleFree (local_path);
749 msg_Err (server, "cannot add trusted CA (%s): %s", ca_path,
750 gnutls_strerror (val));
753 msg_Dbg (server, " %d trusted CA added (%s)", val, ca_path);
755 /* enables peer's certificate verification */
756 sys->handshake = gnutls_HandshakeAndValidate;
763 * Adds a certificates revocation list to be sent to TLS clients.
765 * @param crl_path (Unicode) path of the CRL file.
767 * @return -1 on error, 0 on success.
769 static int gnutls_ServerAddCRL (vlc_tls_creds_t *server, const char *crl_path)
771 vlc_tls_creds_sys_t *sys = server->sys;
772 char *local_path = ToLocale (crl_path);
774 int val = gnutls_certificate_set_x509_crl_file (sys->x509_cred,
776 GNUTLS_X509_FMT_PEM);
777 LocaleFree (local_path);
780 msg_Err (server, "cannot add CRL (%s): %s", crl_path,
781 gnutls_strerror (val));
784 msg_Dbg (server, "%d CRL added (%s)", val, crl_path);
790 * Allocates a whole server's TLS credentials.
792 static int OpenServer (vlc_object_t *obj)
794 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
797 if (gnutls_Init (obj))
800 msg_Dbg (obj, "creating TLS server");
802 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
803 if (unlikely(sys == NULL))
807 server->add_CA = gnutls_ServerAddCA;
808 server->add_CRL = gnutls_ServerAddCRL;
809 server->open = gnutls_SessionOpen;
810 /* No certificate validation by default */
811 sys->handshake = gnutls_ContinueHandshake;
813 /* Sets server's credentials */
814 val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
817 msg_Err (server, "cannot allocate X509 credentials: %s",
818 gnutls_strerror (val));
822 char *cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
823 char *key_path = var_GetNonEmptyString (obj, "tls-x509-key");
824 const char *lcert = ToLocale (cert_path);
825 const char *lkey = ToLocale (key_path);
826 val = gnutls_certificate_set_x509_key_file (sys->x509_cred, lcert, lkey,
827 GNUTLS_X509_FMT_PEM);
835 msg_Err (server, "cannot set certificate chain or private key: %s",
836 gnutls_strerror (val));
837 gnutls_certificate_free_credentials (sys->x509_cred);
842 * - support other cipher suites
844 val = gnutls_dh_params_init (&sys->dh_params);
847 const gnutls_datum_t data = {
848 .data = (unsigned char *)dh_params,
849 .size = sizeof (dh_params) - 1,
852 val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
853 GNUTLS_X509_FMT_PEM);
855 gnutls_certificate_set_dh_params (sys->x509_cred,
860 msg_Err (server, "cannot initialize DHE cipher suites: %s",
861 gnutls_strerror (val));
872 * Destroys a TLS server object.
874 static void CloseServer (vlc_object_t *obj)
876 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
877 vlc_tls_creds_sys_t *sys = server->sys;
879 /* all sessions depending on the server are now deinitialized */
880 gnutls_certificate_free_credentials (sys->x509_cred);
881 gnutls_dh_params_deinit (sys->dh_params);