1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2004-2012 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 Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
16 * You should have received a copy of the GNU Öesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
21 /*****************************************************************************
23 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
36 #include <vlc_block.h>
37 #include <vlc_dialog.h>
39 #include <gnutls/gnutls.h>
40 #include <gnutls/x509.h>
43 /*****************************************************************************
45 *****************************************************************************/
46 static int OpenClient (vlc_tls_creds_t
*);
47 static void CloseClient (vlc_tls_creds_t
*);
48 static int OpenServer (vlc_tls_creds_t
*, const char *, const char *);
49 static void CloseServer (vlc_tls_creds_t
*);
51 #define PRIORITIES_TEXT N_("TLS cipher priorities")
52 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
53 "hash functions and compression methods can be selected. " \
54 "Refer to GNU TLS documentation for detailed syntax.")
55 static const char *const priorities_values
[] = {
62 static const char *const priorities_text
[] = {
63 N_("Performance (prioritize faster ciphers)"),
65 N_("Secure 128-bits (exclude 256-bits ciphers)"),
66 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
67 N_("Export (include insecure ciphers)"),
71 set_shortname( "GNU TLS" )
72 set_description( N_("GNU TLS transport layer security") )
73 set_capability( "tls client", 1 )
74 set_callbacks( OpenClient
, CloseClient
)
75 set_category( CAT_ADVANCED
)
76 set_subcategory( SUBCAT_ADVANCED_NETWORK
)
79 set_description( N_("GNU TLS server") )
80 set_capability( "tls server", 1 )
81 set_category( CAT_ADVANCED
)
82 set_subcategory( SUBCAT_ADVANCED_NETWORK
)
83 set_callbacks( OpenServer
, CloseServer
)
85 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT
,
86 PRIORITIES_LONGTEXT
, false)
87 change_string_list (priorities_values
, priorities_text
)
90 static vlc_mutex_t gnutls_mutex
= VLC_STATIC_MUTEX
;
93 * Initializes GnuTLS with proper locking.
94 * @return VLC_SUCCESS on success, a VLC error code otherwise.
96 static int gnutls_Init (vlc_object_t
*p_this
)
98 int ret
= VLC_EGENERIC
;
100 vlc_mutex_lock (&gnutls_mutex
);
101 if (gnutls_global_init ())
103 msg_Err (p_this
, "cannot initialize GnuTLS");
107 const char *psz_version
= gnutls_check_version ("3.0.20");
108 if (psz_version
== NULL
)
110 msg_Err (p_this
, "unsupported GnuTLS version");
111 gnutls_global_deinit ();
115 msg_Dbg (p_this
, "GnuTLS v%s initialized", psz_version
);
119 vlc_mutex_unlock (&gnutls_mutex
);
125 * Deinitializes GnuTLS.
127 static void gnutls_Deinit (vlc_object_t
*p_this
)
129 vlc_mutex_lock (&gnutls_mutex
);
131 gnutls_global_deinit ();
132 msg_Dbg (p_this
, "GnuTLS deinitialized");
133 vlc_mutex_unlock (&gnutls_mutex
);
137 static int gnutls_Error (vlc_object_t
*obj
, int val
)
143 WSASetLastError (WSAEWOULDBLOCK
);
149 case GNUTLS_E_INTERRUPTED
:
151 WSASetLastError (WSAEINTR
);
158 msg_Err (obj
, "%s", gnutls_strerror (val
));
160 if (!gnutls_error_is_fatal (val
))
161 msg_Err (obj
, "Error above should be handled");
164 WSASetLastError (WSAECONNRESET
);
171 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
175 gnutls_session_t session
;
181 * Sends data through a TLS session.
183 static int gnutls_Send (void *opaque
, const void *buf
, size_t length
)
185 vlc_tls_t
*session
= opaque
;
186 vlc_tls_sys_t
*sys
= session
->sys
;
188 int val
= gnutls_record_send (sys
->session
, buf
, length
);
189 return (val
< 0) ? gnutls_Error (session
, val
) : val
;
194 * Receives data through a TLS session.
196 static int gnutls_Recv (void *opaque
, void *buf
, size_t length
)
198 vlc_tls_t
*session
= opaque
;
199 vlc_tls_sys_t
*sys
= session
->sys
;
201 int val
= gnutls_record_recv (sys
->session
, buf
, length
);
202 return (val
< 0) ? gnutls_Error (session
, val
) : val
;
207 * Starts or continues the TLS handshake.
209 * @return -1 on fatal error, 0 on successful handshake completion,
210 * 1 if more would-be blocking recv is needed,
211 * 2 if more would-be blocking send is required.
213 static int gnutls_ContinueHandshake (vlc_tls_t
*session
, const char *host
,
216 vlc_tls_sys_t
*sys
= session
->sys
;
224 val
= gnutls_handshake (sys
->session
);
225 msg_Dbg (session
, "TLS handshake: %s", gnutls_strerror (val
));
227 if ((val
== GNUTLS_E_AGAIN
) || (val
== GNUTLS_E_INTERRUPTED
))
228 /* I/O event: return to caller's poll() loop */
229 return 1 + gnutls_record_get_direction (sys
->session
);
231 while (val
< 0 && !gnutls_error_is_fatal (val
));
236 msg_Dbg (session
, "Winsock error %d", WSAGetLastError ());
238 msg_Err (session
, "TLS handshake error: %s", gnutls_strerror (val
));
242 sys
->handshaked
= true;
243 (void) host
; (void) service
;
249 * Looks up certificate in known hosts data base.
250 * @return 0 on success, -1 on failure.
252 static int gnutls_CertSearch (vlc_tls_t
*obj
, const char *host
,
254 const gnutls_datum_t
*restrict datum
)
256 assert (host
!= NULL
);
258 /* Look up mismatching certificate in store */
259 int val
= gnutls_verify_stored_pubkey (NULL
, NULL
, host
, service
,
260 GNUTLS_CRT_X509
, datum
, 0);
265 msg_Dbg (obj
, "certificate key match for %s", host
);
267 case GNUTLS_E_NO_CERTIFICATE_FOUND
:
268 msg_Dbg (obj
, "no known certificates for %s", host
);
269 msg
= N_("You attempted to reach %s. "
270 "However the security certificate presented by the server "
271 "is unknown and could not be authenticated by any trusted "
272 "Certification Authority. "
273 "This problem may be caused by a configuration error "
274 "or an attempt to breach your security or your privacy.\n\n"
275 "If in doubt, abort now.\n");
277 case GNUTLS_E_CERTIFICATE_KEY_MISMATCH
:
278 msg_Dbg (obj
, "certificate keys mismatch for %s", host
);
279 msg
= N_("You attempted to reach %s. "
280 "However the security certificate presented by the server "
281 "changed since the previous visit "
282 "and was not authenticated by any trusted "
283 "Certification Authority. "
284 "This problem may be caused by a configuration error "
285 "or an attempt to breach your security or your privacy.\n\n"
286 "If in doubt, abort now.\n");
289 msg_Err (obj
, "certificate key match error for %s: %s", host
,
290 gnutls_strerror (val
));
294 if (dialog_Question (obj
, _("Insecure site"), vlc_gettext (msg
),
295 _("Abort"), _("View certificate"), NULL
, host
) != 2)
298 gnutls_x509_crt_t cert
;
301 if (gnutls_x509_crt_init (&cert
))
303 if (gnutls_x509_crt_import (cert
, datum
, GNUTLS_X509_FMT_DER
)
304 || gnutls_x509_crt_print (cert
, GNUTLS_CRT_PRINT_ONELINE
, &desc
))
306 gnutls_x509_crt_deinit (cert
);
309 gnutls_x509_crt_deinit (cert
);
311 val
= dialog_Question (obj
, _("Insecure site"),
312 _("This is the certificate presented by %s:\n%s\n\n"
313 "If in doubt, abort now.\n"),
314 _("Abort"), _("Accept 24 hours"),
315 _("Accept permanently"), host
, desc
.data
);
316 gnutls_free (desc
.data
);
323 expiry
+= 24 * 60 * 60;
325 val
= gnutls_store_pubkey (NULL
, NULL
, host
, service
,
326 GNUTLS_CRT_X509
, datum
, expiry
, 0);
328 msg_Err (obj
, "cannot store X.509 certificate: %s",
329 gnutls_strerror (val
));
342 { GNUTLS_CERT_INVALID
, "Certificate not verified" },
343 { GNUTLS_CERT_REVOKED
, "Certificate revoked" },
344 { GNUTLS_CERT_SIGNER_NOT_FOUND
, "Signer not found" },
345 { GNUTLS_CERT_SIGNER_NOT_CA
, "Signer not a CA" },
346 { GNUTLS_CERT_INSECURE_ALGORITHM
, "Signature algorithm insecure" },
347 { GNUTLS_CERT_NOT_ACTIVATED
, "Certificate not activated" },
348 { GNUTLS_CERT_EXPIRED
, "Certificate expired" },
352 static int gnutls_HandshakeAndValidate (vlc_tls_t
*session
, const char *host
,
355 vlc_tls_sys_t
*sys
= session
->sys
;
357 int val
= gnutls_ContinueHandshake (session
, host
, service
);
361 /* certificates chain verification */
364 val
= gnutls_certificate_verify_peers2 (sys
->session
, &status
);
367 msg_Err (session
, "Certificate verification error: %s",
368 gnutls_strerror (val
));
373 msg_Err (session
, "Certificate verification failure (0x%04X)", status
);
374 for (size_t i
= 0; i
< sizeof (cert_errs
) / sizeof (cert_errs
[0]); i
++)
375 if (status
& cert_errs
[i
].flag
)
376 msg_Err (session
, " * %s", cert_errs
[i
].msg
);
377 if (status
& ~(GNUTLS_CERT_INVALID
|GNUTLS_CERT_SIGNER_NOT_FOUND
))
381 /* certificate (host)name verification */
382 const gnutls_datum_t
*data
;
384 data
= gnutls_certificate_get_peers (sys
->session
, &count
);
385 if (data
== NULL
|| count
== 0)
387 msg_Err (session
, "Peer certificate not available");
390 msg_Dbg (session
, "%u certificate(s) in the list", count
);
392 if (val
|| host
== NULL
)
394 if (status
&& gnutls_CertSearch (session
, host
, service
, data
))
397 gnutls_x509_crt_t cert
;
398 val
= gnutls_x509_crt_init (&cert
);
401 msg_Err (session
, "X.509 fatal error: %s", gnutls_strerror (val
));
405 val
= gnutls_x509_crt_import (cert
, data
, GNUTLS_X509_FMT_DER
);
408 msg_Err (session
, "Certificate import error: %s",
409 gnutls_strerror (val
));
413 val
= !gnutls_x509_crt_check_hostname (cert
, host
);
416 msg_Err (session
, "Certificate does not match \"%s\"", host
);
417 val
= gnutls_CertSearch (session
, host
, service
, data
);
420 gnutls_x509_crt_deinit (cert
);
425 gnutls_SessionPrioritize (vlc_object_t
*obj
, gnutls_session_t session
)
427 char *priorities
= var_InheritString (obj
, "gnutls-priorities");
428 if (unlikely(priorities
== NULL
))
432 int val
= gnutls_priority_set_direct (session
, priorities
, &errp
);
435 msg_Err (obj
, "cannot set TLS priorities \"%s\": %s", errp
,
436 gnutls_strerror (val
));
447 * TLS credentials private data
449 struct vlc_tls_creds_sys
451 gnutls_certificate_credentials_t x509_cred
;
452 gnutls_dh_params_t dh_params
; /* XXX: used for server only */
453 int (*handshake
) (vlc_tls_t
*, const char *, const char *);
454 /* ^^ XXX: useful for server only */
459 * Terminates TLS session and releases session data.
460 * You still have to close the socket yourself.
462 static void gnutls_SessionClose (vlc_tls_creds_t
*crd
, vlc_tls_t
*session
)
464 vlc_tls_sys_t
*sys
= session
->sys
;
467 gnutls_bye (sys
->session
, GNUTLS_SHUT_WR
);
468 gnutls_deinit (sys
->session
);
476 * Initializes a server-side TLS session.
478 static int gnutls_SessionOpen (vlc_tls_creds_t
*crd
, vlc_tls_t
*session
,
481 vlc_tls_sys_t
*sys
= malloc (sizeof (*session
->sys
));
482 if (unlikely(sys
== NULL
))
486 session
->sock
.p_sys
= session
;
487 session
->sock
.pf_send
= gnutls_Send
;
488 session
->sock
.pf_recv
= gnutls_Recv
;
489 session
->handshake
= crd
->sys
->handshake
;
490 sys
->handshaked
= false;
492 int val
= gnutls_init (&sys
->session
, type
);
495 msg_Err (session
, "cannot initialize TLS session: %s",
496 gnutls_strerror (val
));
501 if (gnutls_SessionPrioritize (VLC_OBJECT (crd
), sys
->session
))
504 val
= gnutls_credentials_set (sys
->session
, GNUTLS_CRD_CERTIFICATE
,
505 crd
->sys
->x509_cred
);
508 msg_Err (session
, "cannot set TLS session credentials: %s",
509 gnutls_strerror (val
));
513 gnutls_transport_set_ptr (sys
->session
,
514 (gnutls_transport_ptr_t
)(intptr_t)fd
);
518 gnutls_SessionClose (crd
, session
);
522 static int gnutls_ServerSessionOpen (vlc_tls_creds_t
*crd
, vlc_tls_t
*session
,
523 int fd
, const char *hostname
)
525 int val
= gnutls_SessionOpen (crd
, session
, GNUTLS_SERVER
, fd
);
526 if (val
!= VLC_SUCCESS
)
529 if (session
->handshake
== gnutls_HandshakeAndValidate
)
530 gnutls_certificate_server_set_request (session
->sys
->session
,
531 GNUTLS_CERT_REQUIRE
);
532 assert (hostname
== NULL
);
536 static int gnutls_ClientSessionOpen (vlc_tls_creds_t
*crd
, vlc_tls_t
*session
,
537 int fd
, const char *hostname
)
539 int val
= gnutls_SessionOpen (crd
, session
, GNUTLS_CLIENT
, fd
);
540 if (val
!= VLC_SUCCESS
)
543 vlc_tls_sys_t
*sys
= session
->sys
;
545 /* minimum DH prime bits */
546 gnutls_dh_set_prime_bits (sys
->session
, 1024);
548 if (likely(hostname
!= NULL
))
549 /* fill Server Name Indication */
550 gnutls_server_name_set (sys
->session
, GNUTLS_NAME_DNS
,
551 hostname
, strlen (hostname
));
558 * Adds one or more Certificate Authorities to the trusted set.
560 * @param path (UTF-8) path to an X.509 certificates list.
562 * @return -1 on error, 0 on success.
564 static int gnutls_AddCA (vlc_tls_creds_t
*crd
, const char *path
)
566 block_t
*block
= block_FilePath (path
);
569 msg_Err (crd
, "cannot read trusted CA from %s: %m", path
);
574 .data
= block
->p_buffer
,
575 .size
= block
->i_buffer
,
578 int val
= gnutls_certificate_set_x509_trust_mem (crd
->sys
->x509_cred
, &d
,
579 GNUTLS_X509_FMT_PEM
);
580 block_Release (block
);
583 msg_Err (crd
, "cannot load trusted CA from %s: %s", path
,
584 gnutls_strerror (val
));
587 msg_Dbg (crd
, " %d trusted CA%s added from %s", val
, (val
!= 1) ? "s" : "",
590 /* enables peer's certificate verification */
591 crd
->sys
->handshake
= gnutls_HandshakeAndValidate
;
597 * Adds a Certificates Revocation List to be sent to TLS clients.
599 * @param path (UTF-8) path of the CRL file.
601 * @return -1 on error, 0 on success.
603 static int gnutls_AddCRL (vlc_tls_creds_t
*crd
, const char *path
)
605 block_t
*block
= block_FilePath (path
);
608 msg_Err (crd
, "cannot read CRL from %s: %m", path
);
613 .data
= block
->p_buffer
,
614 .size
= block
->i_buffer
,
617 int val
= gnutls_certificate_set_x509_crl_mem (crd
->sys
->x509_cred
, &d
,
618 GNUTLS_X509_FMT_PEM
);
619 block_Release (block
);
622 msg_Err (crd
, "cannot add CRL (%s): %s", path
, gnutls_strerror (val
));
625 msg_Dbg (crd
, "%d CRL%s added from %s", val
, (val
!= 1) ? "s" : "", path
);
631 * Allocates a whole server's TLS credentials.
633 static int OpenServer (vlc_tls_creds_t
*crd
, const char *cert
, const char *key
)
637 if (gnutls_Init (VLC_OBJECT(crd
)))
640 vlc_tls_creds_sys_t
*sys
= malloc (sizeof (*sys
));
641 if (unlikely(sys
== NULL
))
645 crd
->add_CA
= gnutls_AddCA
;
646 crd
->add_CRL
= gnutls_AddCRL
;
647 crd
->open
= gnutls_ServerSessionOpen
;
648 crd
->close
= gnutls_SessionClose
;
649 /* No certificate validation by default */
650 sys
->handshake
= gnutls_ContinueHandshake
;
652 /* Sets server's credentials */
653 val
= gnutls_certificate_allocate_credentials (&sys
->x509_cred
);
656 msg_Err (crd
, "cannot allocate credentials: %s",
657 gnutls_strerror (val
));
661 block_t
*certblock
= block_FilePath (cert
);
662 if (certblock
== NULL
)
664 msg_Err (crd
, "cannot read certificate chain from %s: %m", cert
);
668 block_t
*keyblock
= block_FilePath (key
);
669 if (keyblock
== NULL
)
671 msg_Err (crd
, "cannot read private key from %s: %m", key
);
672 block_Release (certblock
);
676 gnutls_datum_t pub
= {
677 .data
= certblock
->p_buffer
,
678 .size
= certblock
->i_buffer
,
680 .data
= keyblock
->p_buffer
,
681 .size
= keyblock
->i_buffer
,
684 val
= gnutls_certificate_set_x509_key_mem (sys
->x509_cred
, &pub
, &priv
,
685 GNUTLS_X509_FMT_PEM
);
686 block_Release (keyblock
);
687 block_Release (certblock
);
690 msg_Err (crd
, "cannot load X.509 key: %s", gnutls_strerror (val
));
691 gnutls_certificate_free_credentials (sys
->x509_cred
);
696 * - support other cipher suites
698 val
= gnutls_dh_params_init (&sys
->dh_params
);
701 const gnutls_datum_t data
= {
702 .data
= (unsigned char *)dh_params
,
703 .size
= sizeof (dh_params
) - 1,
706 val
= gnutls_dh_params_import_pkcs3 (sys
->dh_params
, &data
,
707 GNUTLS_X509_FMT_PEM
);
709 gnutls_certificate_set_dh_params (sys
->x509_cred
,
714 msg_Err (crd
, "cannot initialize DHE cipher suites: %s",
715 gnutls_strerror (val
));
722 gnutls_Deinit (VLC_OBJECT(crd
));
727 * Destroys a TLS server object.
729 static void CloseServer (vlc_tls_creds_t
*crd
)
731 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
733 /* all sessions depending on the server are now deinitialized */
734 gnutls_certificate_free_credentials (sys
->x509_cred
);
735 gnutls_dh_params_deinit (sys
->dh_params
);
738 gnutls_Deinit (VLC_OBJECT(crd
));
742 * Initializes a client-side TLS credentials.
744 static int OpenClient (vlc_tls_creds_t
*crd
)
746 if (gnutls_Init (VLC_OBJECT(crd
)))
749 vlc_tls_creds_sys_t
*sys
= malloc (sizeof (*sys
));
750 if (unlikely(sys
== NULL
))
754 //crd->add_CA = gnutls_AddCA;
755 //crd->add_CRL = gnutls_AddCRL;
756 crd
->open
= gnutls_ClientSessionOpen
;
757 crd
->close
= gnutls_SessionClose
;
758 sys
->handshake
= gnutls_HandshakeAndValidate
;
760 int val
= gnutls_certificate_allocate_credentials (&sys
->x509_cred
);
763 msg_Err (crd
, "cannot allocate credentials: %s",
764 gnutls_strerror (val
));
768 val
= gnutls_certificate_set_x509_system_trust (sys
->x509_cred
);
770 msg_Err (crd
, "cannot load trusted Certificate Authorities: %s",
771 gnutls_strerror (val
));
773 msg_Dbg (crd
, "loaded %d trusted CAs", val
);
775 gnutls_certificate_set_verify_flags (sys
->x509_cred
,
776 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT
);
781 gnutls_Deinit (VLC_OBJECT(crd
));
785 static void CloseClient (vlc_tls_creds_t
*crd
)
787 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
789 gnutls_certificate_free_credentials (sys
->x509_cred
);
792 gnutls_Deinit (VLC_OBJECT(crd
));