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 *****************************************************************************/
30 #include <sys/types.h>
37 # include <wincrypt.h>
43 #include <vlc_common.h>
44 #include <vlc_plugin.h>
46 #include <vlc_charset.h>
48 #include <vlc_block.h>
51 #include <gnutls/gnutls.h>
52 #include <gnutls/x509.h>
54 #include <vlc_gcrypt.h>
59 /*****************************************************************************
61 *****************************************************************************/
62 static int OpenClient (vlc_tls_t *, int, const char *);
63 static void CloseClient (vlc_tls_t *);
64 static int OpenServer (vlc_object_t *);
65 static void CloseServer (vlc_object_t *);
67 #define PRIORITIES_TEXT N_("TLS cipher priorities")
68 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
69 "hash functions and compression methods can be selected. " \
70 "Refer to GNU TLS documentation for detailed syntax.")
71 static const char *const priorities_values[] = {
78 static const char *const priorities_text[] = {
79 N_("Performance (prioritize faster ciphers)"),
81 N_("Secure 128-bits (exclude 256-bits ciphers)"),
82 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
83 N_("Export (include insecure ciphers)"),
87 set_shortname( "GNU TLS" )
88 set_description( N_("GNU TLS transport layer security") )
89 set_capability( "tls client", 1 )
90 set_callbacks( OpenClient, CloseClient )
91 set_category( CAT_ADVANCED )
92 set_subcategory( SUBCAT_ADVANCED_MISC )
95 set_description( N_("GNU TLS server") )
96 set_capability( "tls server", 1 )
97 set_category( CAT_ADVANCED )
98 set_subcategory( SUBCAT_ADVANCED_MISC )
99 set_callbacks( OpenServer, CloseServer )
101 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
102 PRIORITIES_LONGTEXT, false)
103 change_string_list (priorities_values, priorities_text)
106 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
109 * Initializes GnuTLS with proper locking.
110 * @return VLC_SUCCESS on success, a VLC error code otherwise.
112 static int gnutls_Init (vlc_object_t *p_this)
114 int ret = VLC_EGENERIC;
116 vlc_gcrypt_init (); /* GnuTLS depends on gcrypt */
118 vlc_mutex_lock (&gnutls_mutex);
119 if (gnutls_global_init ())
121 msg_Err (p_this, "cannot initialize GnuTLS");
125 const char *psz_version = gnutls_check_version ("2.0.0");
126 if (psz_version == NULL)
128 msg_Err (p_this, "unsupported GnuTLS version");
129 gnutls_global_deinit ();
133 msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
137 vlc_mutex_unlock (&gnutls_mutex);
143 * Deinitializes GnuTLS.
145 static void gnutls_Deinit (vlc_object_t *p_this)
147 vlc_mutex_lock (&gnutls_mutex);
149 gnutls_global_deinit ();
150 msg_Dbg (p_this, "GnuTLS deinitialized");
151 vlc_mutex_unlock (&gnutls_mutex);
155 static int gnutls_Error (vlc_object_t *obj, int val)
161 WSASetLastError (WSAEWOULDBLOCK);
167 case GNUTLS_E_INTERRUPTED:
169 WSASetLastError (WSAEINTR);
176 msg_Err (obj, "%s", gnutls_strerror (val));
178 if (!gnutls_error_is_fatal (val))
179 msg_Err (obj, "Error above should be handled");
182 WSASetLastError (WSAECONNRESET);
189 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
194 gnutls_session_t session;
195 gnutls_certificate_credentials_t x509_cred;
202 * Sends data through a TLS session.
204 static int gnutls_Send (void *opaque, const void *buf, size_t length)
206 vlc_tls_t *session = opaque;
207 vlc_tls_sys_t *sys = session->sys;
209 int val = gnutls_record_send (sys->session, buf, length);
210 return (val < 0) ? gnutls_Error (session, val) : val;
215 * Receives data through a TLS session.
217 static int gnutls_Recv (void *opaque, void *buf, size_t length)
219 vlc_tls_t *session = opaque;
220 vlc_tls_sys_t *sys = session->sys;
222 int val = gnutls_record_recv (sys->session, buf, length);
223 return (val < 0) ? gnutls_Error (session, val) : val;
228 * Starts or continues the TLS handshake.
230 * @return -1 on fatal error, 0 on successful handshake completion,
231 * 1 if more would-be blocking recv is needed,
232 * 2 if more would-be blocking send is required.
234 static int gnutls_ContinueHandshake (vlc_tls_t *session)
236 vlc_tls_sys_t *sys = session->sys;
242 val = gnutls_handshake (sys->session);
243 if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
244 return 1 + gnutls_record_get_direction (sys->session);
249 msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
251 msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
255 sys->handshaked = true;
266 static const error_msg_t cert_errors[] =
268 { GNUTLS_CERT_INVALID,
269 "Certificate could not be verified" },
270 { GNUTLS_CERT_REVOKED,
271 "Certificate was revoked" },
272 { GNUTLS_CERT_SIGNER_NOT_FOUND,
273 "Certificate's signer was not found" },
274 { GNUTLS_CERT_SIGNER_NOT_CA,
275 "Certificate's signer is not a CA" },
276 { GNUTLS_CERT_INSECURE_ALGORITHM,
277 "Insecure certificate signature algorithm" },
278 { GNUTLS_CERT_NOT_ACTIVATED,
279 "Certificate is not yet activated" },
280 { GNUTLS_CERT_EXPIRED,
281 "Certificate has expired" },
286 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
288 vlc_tls_sys_t *sys = session->sys;
290 int val = gnutls_ContinueHandshake (session);
294 /* certificates chain verification */
297 val = gnutls_certificate_verify_peers2 (sys->session, &status);
300 msg_Err (session, "Certificate verification failed: %s",
301 gnutls_strerror (val));
307 msg_Err (session, "TLS session: access denied (status 0x%X)", status);
308 for (const error_msg_t *e = cert_errors; e->flag; e++)
310 if (status & e->flag)
312 msg_Err (session, "%s", e->msg);
319 "unknown certificate error (you found a bug in VLC)");
323 /* certificate (host)name verification */
324 const gnutls_datum_t *data;
325 data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
328 msg_Err (session, "Peer certificate not available");
332 gnutls_x509_crt_t cert;
333 val = gnutls_x509_crt_init (&cert);
336 msg_Err (session, "x509 fatal error: %s", gnutls_strerror (val));
340 val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
343 msg_Err (session, "Certificate import error: %s",
344 gnutls_strerror (val));
348 if (sys->hostname != NULL
349 && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
351 msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
355 gnutls_x509_crt_deinit (cert);
356 msg_Dbg (session, "TLS/x509 certificate verified");
360 gnutls_x509_crt_deinit (cert);
365 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
367 char *priorities = var_InheritString (obj, "gnutls-priorities");
368 if (unlikely(priorities == NULL))
372 int val = gnutls_priority_set_direct (session, priorities, &errp);
375 msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
376 gnutls_strerror (val));
387 * Loads x509 credentials from a file descriptor (directory or regular file)
388 * and closes the descriptor.
390 static void gnutls_x509_AddFD (vlc_object_t *obj,
391 gnutls_certificate_credentials_t cred,
392 int fd, bool priv, unsigned recursion)
394 DIR *dir = fdopendir (fd);
403 char *ent = vlc_readdir (dir);
407 if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
413 int nfd = vlc_openat (fd, ent, O_RDONLY);
416 msg_Dbg (obj, "loading x509 credentials from %s...", ent);
417 gnutls_x509_AddFD (obj, cred, nfd, priv, recursion);
420 msg_Dbg (obj, "cannot access x509 credentials in %s", ent);
428 block_t *block = block_File (fd);
431 gnutls_datum_t data = {
432 .data = block->p_buffer,
433 .size = block->i_buffer,
436 ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
438 : gnutls_certificate_set_x509_trust_mem (cred, &data,
439 GNUTLS_X509_FMT_PEM);
440 block_Release (block);
443 msg_Warn (obj, "cannot add x509 credentials: %s",
444 gnutls_strerror (res));
446 msg_Dbg (obj, "added %d %s(s)", res, priv ? "key" : "certificate");
449 msg_Warn (obj, "cannot read x509 credentials: %m");
453 static void gnutls_x509_AddPath (vlc_object_t *obj,
454 gnutls_certificate_credentials_t cred,
455 const char *path, bool priv)
457 msg_Dbg (obj, "loading x509 credentials in %s...", path);
458 int fd = vlc_open (path, O_RDONLY);
461 msg_Warn (obj, "cannot access x509 in %s: %m", path);
465 gnutls_x509_AddFD (obj, cred, fd, priv, 5);
469 gnutls_loadOSCAList (vlc_object_t *p_this,
470 gnutls_certificate_credentials cred)
472 HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
475 msg_Warn (p_this, "could not open the Cert SystemStore");
479 PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
480 while( pCertContext )
482 gnutls_datum data = {
483 .data = pCertContext->pbCertEncoded,
484 .size = pCertContext->cbCertEncoded,
487 if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
489 msg_Warn (p_this, "cannot add x509 credential");
493 pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
500 * Initializes a client-side TLS session.
502 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
504 if (gnutls_Init (VLC_OBJECT(session)))
507 vlc_tls_sys_t *sys = malloc (sizeof (*sys));
508 if (unlikely(sys == NULL))
510 gnutls_Deinit (VLC_OBJECT(session));
515 session->sock.p_sys = session;
516 session->sock.pf_send = gnutls_Send;
517 session->sock.pf_recv = gnutls_Recv;
518 sys->handshaked = false;
520 int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
523 msg_Err (session, "cannot allocate X509 credentials: %s",
524 gnutls_strerror (val));
529 char *userdir = config_GetUserDir (VLC_DATA_DIR);
532 char path[strlen (userdir) + sizeof ("/ssl/private/")];
533 sprintf (path, "%s/ssl", userdir);
534 vlc_mkdir (path, 0755);
536 sprintf (path, "%s/ssl/certs/", userdir);
537 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
538 sprintf (path, "%s/ssl/private/", userdir);
539 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, true);
543 const char *confdir = config_GetConfDir ();
545 char path[strlen (confdir)
546 + sizeof ("/ssl/certs/ca-certificates.crt")];
547 sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
548 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
551 gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
553 gnutls_certificate_set_verify_flags (sys->x509_cred,
554 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
556 session->handshake = gnutls_HandshakeAndValidate;
557 /*session->_handshake = gnutls_ContinueHandshake;*/
559 val = gnutls_init (&sys->session, GNUTLS_CLIENT);
562 msg_Err (session, "cannot initialize TLS session: %s",
563 gnutls_strerror (val));
564 gnutls_certificate_free_credentials (sys->x509_cred);
568 if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
571 /* minimum DH prime bits */
572 gnutls_dh_set_prime_bits (sys->session, 1024);
574 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
578 msg_Err (session, "cannot set TLS session credentials: %s",
579 gnutls_strerror (val));
584 if (likely(hostname != NULL))
586 /* fill Server Name Indication */
587 gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
588 hostname, strlen (hostname));
589 /* keep hostname to match CNAME after handshake */
590 sys->hostname = strdup (hostname);
591 if (unlikely(sys->hostname == NULL))
595 sys->hostname = NULL;
597 gnutls_transport_set_ptr (sys->session,
598 (gnutls_transport_ptr_t)(intptr_t)fd);
602 gnutls_deinit (sys->session);
603 gnutls_certificate_free_credentials (sys->x509_cred);
605 gnutls_Deinit (VLC_OBJECT(session));
611 static void CloseClient (vlc_tls_t *session)
613 vlc_tls_sys_t *sys = session->sys;
616 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
617 gnutls_deinit (sys->session);
618 /* credentials must be free'd *after* gnutls_deinit() */
619 gnutls_certificate_free_credentials (sys->x509_cred);
621 gnutls_Deinit (VLC_OBJECT(session));
622 free (sys->hostname);
630 struct vlc_tls_creds_sys
632 gnutls_certificate_credentials_t x509_cred;
633 gnutls_dh_params_t dh_params;
634 int (*handshake) (vlc_tls_t *);
639 * Terminates TLS session and releases session data.
640 * You still have to close the socket yourself.
642 static void gnutls_SessionClose (vlc_tls_t *session)
644 vlc_tls_sys_t *sys = session->sys;
647 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
648 gnutls_deinit (sys->session);
650 vlc_object_release (session);
656 * Initializes a server-side TLS session.
658 static vlc_tls_t *gnutls_SessionOpen (vlc_tls_creds_t *server, int fd)
660 vlc_tls_creds_sys_t *ssys = server->sys;
663 vlc_tls_t *session = vlc_object_create (server, sizeof (*session));
664 if (unlikely(session == NULL))
667 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
668 if (unlikely(sys == NULL))
670 vlc_object_release (session);
675 session->sock.p_sys = session;
676 session->sock.pf_send = gnutls_Send;
677 session->sock.pf_recv = gnutls_Recv;
678 session->handshake = ssys->handshake;
679 session->u.close = gnutls_SessionClose;
680 sys->handshaked = false;
681 sys->hostname = NULL;
683 val = gnutls_init (&sys->session, GNUTLS_SERVER);
686 msg_Err (server, "cannot initialize TLS session: %s",
687 gnutls_strerror (val));
689 vlc_object_release (session);
693 if (gnutls_SessionPrioritize (VLC_OBJECT (server), sys->session))
696 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
700 msg_Err (server, "cannot set TLS session credentials: %s",
701 gnutls_strerror (val));
705 if (session->handshake == gnutls_HandshakeAndValidate)
706 gnutls_certificate_server_set_request (sys->session,
707 GNUTLS_CERT_REQUIRE);
709 gnutls_transport_set_ptr (sys->session,
710 (gnutls_transport_ptr_t)(intptr_t)fd);
714 gnutls_SessionClose (session);
720 * Adds one or more certificate authorities.
722 * @param ca_path (Unicode) path to an x509 certificates list.
724 * @return -1 on error, 0 on success.
726 static int gnutls_ServerAddCA (vlc_tls_creds_t *server, const char *ca_path)
728 vlc_tls_creds_sys_t *sys = server->sys;
729 const char *local_path = ToLocale (ca_path);
731 int val = gnutls_certificate_set_x509_trust_file (sys->x509_cred,
733 GNUTLS_X509_FMT_PEM );
734 LocaleFree (local_path);
737 msg_Err (server, "cannot add trusted CA (%s): %s", ca_path,
738 gnutls_strerror (val));
741 msg_Dbg (server, " %d trusted CA added (%s)", val, ca_path);
743 /* enables peer's certificate verification */
744 sys->handshake = gnutls_HandshakeAndValidate;
751 * Adds a certificates revocation list to be sent to TLS clients.
753 * @param crl_path (Unicode) path of the CRL file.
755 * @return -1 on error, 0 on success.
757 static int gnutls_ServerAddCRL (vlc_tls_creds_t *server, const char *crl_path)
759 vlc_tls_creds_sys_t *sys = server->sys;
760 const char *local_path = ToLocale (crl_path);
762 int val = gnutls_certificate_set_x509_crl_file (sys->x509_cred,
764 GNUTLS_X509_FMT_PEM);
765 LocaleFree (local_path);
768 msg_Err (server, "cannot add CRL (%s): %s", crl_path,
769 gnutls_strerror (val));
772 msg_Dbg (server, "%d CRL added (%s)", val, crl_path);
778 * Allocates a whole server's TLS credentials.
780 static int OpenServer (vlc_object_t *obj)
782 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
785 if (gnutls_Init (obj))
788 msg_Dbg (obj, "creating TLS server");
790 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
791 if (unlikely(sys == NULL))
795 server->add_CA = gnutls_ServerAddCA;
796 server->add_CRL = gnutls_ServerAddCRL;
797 server->open = gnutls_SessionOpen;
798 /* No certificate validation by default */
799 sys->handshake = gnutls_ContinueHandshake;
801 /* Sets server's credentials */
802 val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
805 msg_Err (server, "cannot allocate X509 credentials: %s",
806 gnutls_strerror (val));
810 char *cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
811 char *key_path = var_GetNonEmptyString (obj, "tls-x509-key");
812 const char *lcert = ToLocale (cert_path);
813 const char *lkey = ToLocale (key_path);
814 val = gnutls_certificate_set_x509_key_file (sys->x509_cred, lcert, lkey,
815 GNUTLS_X509_FMT_PEM);
823 msg_Err (server, "cannot set certificate chain or private key: %s",
824 gnutls_strerror (val));
825 gnutls_certificate_free_credentials (sys->x509_cred);
830 * - support other cipher suites
832 val = gnutls_dh_params_init (&sys->dh_params);
835 const gnutls_datum_t data = {
836 .data = (unsigned char *)dh_params,
837 .size = sizeof (dh_params) - 1,
840 val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
841 GNUTLS_X509_FMT_PEM);
843 gnutls_certificate_set_dh_params (sys->x509_cred,
848 msg_Err (server, "cannot initialize DHE cipher suites: %s",
849 gnutls_strerror (val));
860 * Destroys a TLS server object.
862 static void CloseServer (vlc_object_t *obj)
864 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
865 vlc_tls_creds_sys_t *sys = server->sys;
867 /* all sessions depending on the server are now deinitialized */
868 gnutls_certificate_free_credentials (sys->x509_cred);
869 gnutls_dh_params_deinit (sys->dh_params);