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>
50 #include <gnutls/gnutls.h>
51 #include <gnutls/x509.h>
57 /*****************************************************************************
59 *****************************************************************************/
60 static int OpenClient (vlc_tls_t *, int, const char *);
61 static void CloseClient (vlc_tls_t *);
62 static int OpenServer (vlc_object_t *);
63 static void CloseServer (vlc_object_t *);
65 #define PRIORITIES_TEXT N_("TLS cipher priorities")
66 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
67 "hash functions and compression methods can be selected. " \
68 "Refer to GNU TLS documentation for detailed syntax.")
69 static const char *const priorities_values[] = {
76 static const char *const priorities_text[] = {
77 N_("Performance (prioritize faster ciphers)"),
79 N_("Secure 128-bits (exclude 256-bits ciphers)"),
80 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
81 N_("Export (include insecure ciphers)"),
85 set_shortname( "GNU TLS" )
86 set_description( N_("GNU TLS transport layer security") )
87 set_capability( "tls client", 1 )
88 set_callbacks( OpenClient, CloseClient )
89 set_category( CAT_ADVANCED )
90 set_subcategory( SUBCAT_ADVANCED_MISC )
93 set_description( N_("GNU TLS server") )
94 set_capability( "tls server", 1 )
95 set_category( CAT_ADVANCED )
96 set_subcategory( SUBCAT_ADVANCED_MISC )
97 set_callbacks( OpenServer, CloseServer )
99 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
100 PRIORITIES_LONGTEXT, false)
101 change_string_list (priorities_values, priorities_text)
104 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
107 * Initializes GnuTLS with proper locking.
108 * @return VLC_SUCCESS on success, a VLC error code otherwise.
110 static int gnutls_Init (vlc_object_t *p_this)
112 int ret = VLC_EGENERIC;
114 vlc_mutex_lock (&gnutls_mutex);
115 if (gnutls_global_init ())
117 msg_Err (p_this, "cannot initialize GnuTLS");
121 const char *psz_version = gnutls_check_version ("2.0.0");
122 if (psz_version == NULL)
124 msg_Err (p_this, "unsupported GnuTLS version");
125 gnutls_global_deinit ();
129 msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
133 vlc_mutex_unlock (&gnutls_mutex);
139 * Deinitializes GnuTLS.
141 static void gnutls_Deinit (vlc_object_t *p_this)
143 vlc_mutex_lock (&gnutls_mutex);
145 gnutls_global_deinit ();
146 msg_Dbg (p_this, "GnuTLS deinitialized");
147 vlc_mutex_unlock (&gnutls_mutex);
151 static int gnutls_Error (vlc_object_t *obj, int val)
157 WSASetLastError (WSAEWOULDBLOCK);
163 case GNUTLS_E_INTERRUPTED:
165 WSASetLastError (WSAEINTR);
172 msg_Err (obj, "%s", gnutls_strerror (val));
174 if (!gnutls_error_is_fatal (val))
175 msg_Err (obj, "Error above should be handled");
178 WSASetLastError (WSAECONNRESET);
185 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
190 gnutls_session_t session;
191 gnutls_certificate_credentials_t x509_cred;
198 * Sends data through a TLS session.
200 static int gnutls_Send (void *opaque, const void *buf, size_t length)
202 vlc_tls_t *session = opaque;
203 vlc_tls_sys_t *sys = session->sys;
205 int val = gnutls_record_send (sys->session, buf, length);
206 return (val < 0) ? gnutls_Error (session, val) : val;
211 * Receives data through a TLS session.
213 static int gnutls_Recv (void *opaque, void *buf, size_t length)
215 vlc_tls_t *session = opaque;
216 vlc_tls_sys_t *sys = session->sys;
218 int val = gnutls_record_recv (sys->session, buf, length);
219 return (val < 0) ? gnutls_Error (session, val) : val;
224 * Starts or continues the TLS handshake.
226 * @return -1 on fatal error, 0 on successful handshake completion,
227 * 1 if more would-be blocking recv is needed,
228 * 2 if more would-be blocking send is required.
230 static int gnutls_ContinueHandshake (vlc_tls_t *session)
232 vlc_tls_sys_t *sys = session->sys;
238 val = gnutls_handshake (sys->session);
239 if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
240 return 1 + gnutls_record_get_direction (sys->session);
245 msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
247 msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
251 sys->handshaked = true;
262 static const error_msg_t cert_errors[] =
264 { GNUTLS_CERT_INVALID,
265 "Certificate could not be verified" },
266 { GNUTLS_CERT_REVOKED,
267 "Certificate was revoked" },
268 { GNUTLS_CERT_SIGNER_NOT_FOUND,
269 "Certificate's signer was not found" },
270 { GNUTLS_CERT_SIGNER_NOT_CA,
271 "Certificate's signer is not a CA" },
272 { GNUTLS_CERT_INSECURE_ALGORITHM,
273 "Insecure certificate signature algorithm" },
274 { GNUTLS_CERT_NOT_ACTIVATED,
275 "Certificate is not yet activated" },
276 { GNUTLS_CERT_EXPIRED,
277 "Certificate has expired" },
282 static int gnutls_HandshakeAndValidate (vlc_tls_t *session)
284 vlc_tls_sys_t *sys = session->sys;
286 int val = gnutls_ContinueHandshake (session);
290 /* certificates chain verification */
293 val = gnutls_certificate_verify_peers2 (sys->session, &status);
296 msg_Err (session, "Certificate verification failed: %s",
297 gnutls_strerror (val));
303 msg_Err (session, "TLS session: access denied (status 0x%X)", status);
304 for (const error_msg_t *e = cert_errors; e->flag; e++)
306 if (status & e->flag)
308 msg_Err (session, "%s", e->msg);
315 "unknown certificate error (you found a bug in VLC)");
319 /* certificate (host)name verification */
320 const gnutls_datum_t *data;
321 data = gnutls_certificate_get_peers (sys->session, &(unsigned){0});
324 msg_Err (session, "Peer certificate not available");
328 gnutls_x509_crt_t cert;
329 val = gnutls_x509_crt_init (&cert);
332 msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
336 val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
339 msg_Err (session, "Certificate import error: %s",
340 gnutls_strerror (val));
344 if (sys->hostname != NULL
345 && !gnutls_x509_crt_check_hostname (cert, sys->hostname))
347 msg_Err (session, "Certificate does not match \"%s\"", sys->hostname);
351 gnutls_x509_crt_deinit (cert);
352 msg_Dbg (session, "TLS/X.509 certificate verified");
356 gnutls_x509_crt_deinit (cert);
361 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
363 char *priorities = var_InheritString (obj, "gnutls-priorities");
364 if (unlikely(priorities == NULL))
368 int val = gnutls_priority_set_direct (session, priorities, &errp);
371 msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
372 gnutls_strerror (val));
383 * Loads x509 credentials from a file descriptor (directory or regular file)
384 * and closes the descriptor.
386 static void gnutls_x509_AddFD (vlc_object_t *obj,
387 gnutls_certificate_credentials_t cred,
388 int fd, bool priv, unsigned recursion)
390 DIR *dir = fdopendir (fd);
399 char *ent = vlc_readdir (dir);
403 if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
409 int nfd = vlc_openat (fd, ent, O_RDONLY);
412 msg_Dbg (obj, "loading x509 credentials from %s...", ent);
413 gnutls_x509_AddFD (obj, cred, nfd, priv, recursion);
416 msg_Dbg (obj, "cannot access x509 credentials in %s", ent);
424 block_t *block = block_File (fd);
427 gnutls_datum_t data = {
428 .data = block->p_buffer,
429 .size = block->i_buffer,
432 ? gnutls_certificate_set_x509_key_mem (cred, &data, &data,
434 : gnutls_certificate_set_x509_trust_mem (cred, &data,
435 GNUTLS_X509_FMT_PEM);
436 block_Release (block);
439 msg_Warn (obj, "cannot add x509 credentials: %s",
440 gnutls_strerror (res));
442 msg_Dbg (obj, "added %d %s(s)", res, priv ? "key" : "certificate");
445 msg_Warn (obj, "cannot read x509 credentials: %m");
449 static void gnutls_x509_AddPath (vlc_object_t *obj,
450 gnutls_certificate_credentials_t cred,
451 const char *path, bool priv)
453 msg_Dbg (obj, "loading x509 credentials in %s...", path);
454 int fd = vlc_open (path, O_RDONLY);
457 msg_Warn (obj, "cannot access x509 in %s: %m", path);
461 gnutls_x509_AddFD (obj, cred, fd, priv, 5);
465 gnutls_loadOSCAList (vlc_object_t *p_this,
466 gnutls_certificate_credentials cred)
468 HCERTSTORE hCertStore = CertOpenSystemStoreA((HCRYPTPROV)NULL, "ROOT");
471 msg_Warn (p_this, "could not open the Cert SystemStore");
475 PCCERT_CONTEXT pCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
476 while( pCertContext )
478 gnutls_datum data = {
479 .data = pCertContext->pbCertEncoded,
480 .size = pCertContext->cbCertEncoded,
483 if(!gnutls_certificate_set_x509_trust_mem(cred, &data, GNUTLS_X509_FMT_DER))
485 msg_Warn (p_this, "cannot add x509 credential");
489 pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext);
496 * Initializes a client-side TLS session.
498 static int OpenClient (vlc_tls_t *session, int fd, const char *hostname)
500 if (gnutls_Init (VLC_OBJECT(session)))
503 vlc_tls_sys_t *sys = malloc (sizeof (*sys));
504 if (unlikely(sys == NULL))
506 gnutls_Deinit (VLC_OBJECT(session));
511 session->sock.p_sys = session;
512 session->sock.pf_send = gnutls_Send;
513 session->sock.pf_recv = gnutls_Recv;
514 sys->handshaked = false;
516 int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
519 msg_Err (session, "cannot allocate credentials: %s",
520 gnutls_strerror (val));
525 char *userdir = config_GetUserDir (VLC_DATA_DIR);
528 char path[strlen (userdir) + sizeof ("/ssl/private/")];
529 sprintf (path, "%s/ssl", userdir);
530 vlc_mkdir (path, 0755);
532 sprintf (path, "%s/ssl/certs/", userdir);
533 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
534 sprintf (path, "%s/ssl/private/", userdir);
535 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, true);
539 const char *confdir = config_GetConfDir ();
541 char path[strlen (confdir)
542 + sizeof ("/ssl/certs/ca-certificates.crt")];
543 sprintf (path, "%s/ssl/certs/ca-certificates.crt", confdir);
544 gnutls_x509_AddPath (VLC_OBJECT(session), sys->x509_cred, path, false);
547 gnutls_loadOSCAList (VLC_OBJECT(session), sys->x509_cred);
549 gnutls_certificate_set_verify_flags (sys->x509_cred,
550 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
552 session->handshake = gnutls_HandshakeAndValidate;
553 /*session->_handshake = gnutls_ContinueHandshake;*/
555 val = gnutls_init (&sys->session, GNUTLS_CLIENT);
558 msg_Err (session, "cannot initialize TLS session: %s",
559 gnutls_strerror (val));
560 gnutls_certificate_free_credentials (sys->x509_cred);
564 if (gnutls_SessionPrioritize (VLC_OBJECT(session), sys->session))
567 /* minimum DH prime bits */
568 gnutls_dh_set_prime_bits (sys->session, 1024);
570 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
574 msg_Err (session, "cannot set TLS session credentials: %s",
575 gnutls_strerror (val));
580 if (likely(hostname != NULL))
582 /* fill Server Name Indication */
583 gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
584 hostname, strlen (hostname));
585 /* keep hostname to match CNAME after handshake */
586 sys->hostname = strdup (hostname);
587 if (unlikely(sys->hostname == NULL))
591 sys->hostname = NULL;
593 gnutls_transport_set_ptr (sys->session,
594 (gnutls_transport_ptr_t)(intptr_t)fd);
598 gnutls_deinit (sys->session);
599 gnutls_certificate_free_credentials (sys->x509_cred);
601 gnutls_Deinit (VLC_OBJECT(session));
607 static void CloseClient (vlc_tls_t *session)
609 vlc_tls_sys_t *sys = session->sys;
612 gnutls_bye (sys->session, GNUTLS_SHUT_WR);
613 gnutls_deinit (sys->session);
614 /* credentials must be free'd *after* gnutls_deinit() */
615 gnutls_certificate_free_credentials (sys->x509_cred);
617 gnutls_Deinit (VLC_OBJECT(session));
618 free (sys->hostname);
626 struct vlc_tls_creds_sys
628 gnutls_certificate_credentials_t x509_cred;
629 gnutls_dh_params_t dh_params;
630 int (*handshake) (vlc_tls_t *);
635 * Terminates TLS session and releases session data.
636 * You still have to close the socket yourself.
638 static void gnutls_SessionClose (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);
646 vlc_object_release (session);
652 * Initializes a server-side TLS session.
654 static vlc_tls_t *gnutls_SessionOpen (vlc_tls_creds_t *server, int fd)
656 vlc_tls_creds_sys_t *ssys = server->sys;
659 vlc_tls_t *session = vlc_object_create (server, sizeof (*session));
660 if (unlikely(session == NULL))
663 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
664 if (unlikely(sys == NULL))
666 vlc_object_release (session);
671 session->sock.p_sys = session;
672 session->sock.pf_send = gnutls_Send;
673 session->sock.pf_recv = gnutls_Recv;
674 session->handshake = ssys->handshake;
675 session->u.close = gnutls_SessionClose;
676 sys->handshaked = false;
677 sys->hostname = NULL;
679 val = gnutls_init (&sys->session, GNUTLS_SERVER);
682 msg_Err (server, "cannot initialize TLS session: %s",
683 gnutls_strerror (val));
685 vlc_object_release (session);
689 if (gnutls_SessionPrioritize (VLC_OBJECT (server), sys->session))
692 val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
696 msg_Err (server, "cannot set TLS session credentials: %s",
697 gnutls_strerror (val));
701 if (session->handshake == gnutls_HandshakeAndValidate)
702 gnutls_certificate_server_set_request (sys->session,
703 GNUTLS_CERT_REQUIRE);
705 gnutls_transport_set_ptr (sys->session,
706 (gnutls_transport_ptr_t)(intptr_t)fd);
710 gnutls_SessionClose (session);
716 * Adds one or more certificate authorities.
718 * @param ca_path (Unicode) path to an x509 certificates list.
720 * @return -1 on error, 0 on success.
722 static int gnutls_ServerAddCA (vlc_tls_creds_t *server, const char *ca_path)
724 vlc_tls_creds_sys_t *sys = server->sys;
725 const char *local_path = ToLocale (ca_path);
727 int val = gnutls_certificate_set_x509_trust_file (sys->x509_cred,
729 GNUTLS_X509_FMT_PEM );
730 LocaleFree (local_path);
733 msg_Err (server, "cannot add trusted CA (%s): %s", ca_path,
734 gnutls_strerror (val));
737 msg_Dbg (server, " %d trusted CA added (%s)", val, ca_path);
739 /* enables peer's certificate verification */
740 sys->handshake = gnutls_HandshakeAndValidate;
747 * Adds a certificates revocation list to be sent to TLS clients.
749 * @param crl_path (Unicode) path of the CRL file.
751 * @return -1 on error, 0 on success.
753 static int gnutls_ServerAddCRL (vlc_tls_creds_t *server, const char *crl_path)
755 vlc_tls_creds_sys_t *sys = server->sys;
756 const char *local_path = ToLocale (crl_path);
758 int val = gnutls_certificate_set_x509_crl_file (sys->x509_cred,
760 GNUTLS_X509_FMT_PEM);
761 LocaleFree (local_path);
764 msg_Err (server, "cannot add CRL (%s): %s", crl_path,
765 gnutls_strerror (val));
768 msg_Dbg (server, "%d CRL added (%s)", val, crl_path);
774 * Allocates a whole server's TLS credentials.
776 static int OpenServer (vlc_object_t *obj)
778 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
781 if (gnutls_Init (obj))
784 msg_Dbg (obj, "creating TLS server");
786 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
787 if (unlikely(sys == NULL))
791 server->add_CA = gnutls_ServerAddCA;
792 server->add_CRL = gnutls_ServerAddCRL;
793 server->open = gnutls_SessionOpen;
794 /* No certificate validation by default */
795 sys->handshake = gnutls_ContinueHandshake;
797 /* Sets server's credentials */
798 val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
801 msg_Err (server, "cannot allocate credentials: %s",
802 gnutls_strerror (val));
806 char *cert_path = var_GetNonEmptyString (obj, "tls-x509-cert");
807 char *key_path = var_GetNonEmptyString (obj, "tls-x509-key");
808 const char *lcert = ToLocale (cert_path);
809 const char *lkey = ToLocale (key_path);
810 val = gnutls_certificate_set_x509_key_file (sys->x509_cred, lcert, lkey,
811 GNUTLS_X509_FMT_PEM);
819 msg_Err (server, "cannot set certificate chain or private key: %s",
820 gnutls_strerror (val));
821 gnutls_certificate_free_credentials (sys->x509_cred);
826 * - support other cipher suites
828 val = gnutls_dh_params_init (&sys->dh_params);
831 const gnutls_datum_t data = {
832 .data = (unsigned char *)dh_params,
833 .size = sizeof (dh_params) - 1,
836 val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
837 GNUTLS_X509_FMT_PEM);
839 gnutls_certificate_set_dh_params (sys->x509_cred,
844 msg_Err (server, "cannot initialize DHE cipher suites: %s",
845 gnutls_strerror (val));
857 * Destroys a TLS server object.
859 static void CloseServer (vlc_object_t *obj)
861 vlc_tls_creds_t *server = (vlc_tls_creds_t *)obj;
862 vlc_tls_creds_sys_t *sys = server->sys;
864 /* all sessions depending on the server are now deinitialized */
865 gnutls_certificate_free_credentials (sys->x509_cred);
866 gnutls_dh_params_deinit (sys->dh_params);