1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2004-2017 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 Lesser 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 *****************************************************************************/
36 #include <vlc_common.h>
37 #include <vlc_plugin.h>
39 #include <vlc_block.h>
40 #include <vlc_dialog.h>
42 #include <gnutls/gnutls.h>
43 #include <gnutls/x509.h>
45 typedef struct vlc_tls_gnutls
48 gnutls_session_t session
;
52 static void gnutls_Banner(vlc_object_t
*obj
)
54 msg_Dbg(obj
, "using GnuTLS v%s (built with v"GNUTLS_VERSION
")",
55 gnutls_check_version(NULL
));
58 static int gnutls_Error(vlc_tls_gnutls_t
*priv
, int val
)
64 WSASetLastError (WSAEWOULDBLOCK
);
69 case GNUTLS_E_INTERRUPTED
:
71 WSASetLastError (WSAEINTR
);
77 msg_Err(priv
->obj
, "%s", gnutls_strerror (val
));
79 if (!gnutls_error_is_fatal (val
))
80 msg_Err(priv
->obj
, "Error above should be handled");
83 WSASetLastError (WSAECONNRESET
);
90 static ssize_t
vlc_gnutls_read(gnutls_transport_ptr_t ptr
, void *buf
,
93 vlc_tls_t
*sock
= ptr
;
99 return sock
->ops
->readv(sock
, &iov
, 1);
102 static ssize_t
vlc_gnutls_writev(gnutls_transport_ptr_t ptr
,
103 const giovec_t
*giov
, int iovcnt
)
106 const long iovmax
= IOV_MAX
;
108 const long iovmax
= sysconf(_SC_IOV_MAX
);
110 if (unlikely(iovcnt
> iovmax
))
115 if (unlikely(iovcnt
== 0))
118 vlc_tls_t
*sock
= ptr
;
119 struct iovec iov
[iovcnt
];
121 for (int i
= 0; i
< iovcnt
; i
++)
123 iov
[i
].iov_base
= giov
[i
].iov_base
;
124 iov
[i
].iov_len
= giov
[i
].iov_len
;
127 return sock
->ops
->writev(sock
, iov
, iovcnt
);
130 static int gnutls_GetFD(vlc_tls_t
*tls
, short *restrict events
)
132 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
133 vlc_tls_t
*sock
= gnutls_transport_get_ptr(priv
->session
);
135 return vlc_tls_GetPollFD(sock
, events
);
138 static ssize_t
gnutls_Recv(vlc_tls_t
*tls
, struct iovec
*iov
, unsigned count
)
140 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
141 gnutls_session_t session
= priv
->session
;
146 ssize_t val
= gnutls_record_recv(session
, iov
->iov_base
, iov
->iov_len
);
148 return rcvd
? (ssize_t
)rcvd
: gnutls_Error(priv
, val
);
152 if ((size_t)val
< iov
->iov_len
)
162 static ssize_t
gnutls_Send (vlc_tls_t
*tls
, const struct iovec
*iov
,
165 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
166 gnutls_session_t session
= priv
->session
;
169 if (!gnutls_record_check_corked(session
))
171 gnutls_record_cork(session
);
175 val
= gnutls_record_send(session
, iov
->iov_base
, iov
->iov_len
);
176 if (val
< (ssize_t
)iov
->iov_len
)
184 val
= gnutls_record_uncork(session
, 0);
185 return (val
< 0) ? gnutls_Error(priv
, val
) : val
;
188 static int gnutls_Shutdown(vlc_tls_t
*tls
, bool duplex
)
190 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
191 gnutls_session_t session
= priv
->session
;
194 /* Flush any pending data */
195 val
= gnutls_record_uncork(session
, 0);
197 return gnutls_Error(priv
, val
);
199 val
= gnutls_bye(session
, duplex
? GNUTLS_SHUT_RDWR
: GNUTLS_SHUT_WR
);
201 return gnutls_Error(priv
, val
);
206 static void gnutls_Close (vlc_tls_t
*tls
)
208 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
210 gnutls_deinit(priv
->session
);
214 static const struct vlc_tls_operations gnutls_ops
=
223 static vlc_tls_gnutls_t
*gnutls_SessionOpen(vlc_object_t
*obj
, int type
,
224 gnutls_certificate_credentials_t x509
,
226 const char *const *alpn
)
228 vlc_tls_gnutls_t
*priv
= malloc(sizeof (*priv
));
229 if (unlikely(priv
== NULL
))
232 gnutls_session_t session
;
236 type
|= GNUTLS_NONBLOCK
;
237 #if (GNUTLS_VERSION_NUMBER >= 0x030500)
238 type
|= GNUTLS_ENABLE_FALSE_START
;
241 val
= gnutls_init(&session
, type
);
244 msg_Err(obj
, "cannot initialize TLS session: %s",
245 gnutls_strerror(val
));
250 char *priorities
= var_InheritString(obj
, "gnutls-priorities");
251 if (unlikely(priorities
== NULL
))
254 val
= gnutls_priority_set_direct (session
, priorities
, &errp
);
256 msg_Err(obj
, "cannot set TLS priorities \"%s\": %s", errp
,
257 gnutls_strerror(val
));
262 val
= gnutls_credentials_set (session
, GNUTLS_CRD_CERTIFICATE
, x509
);
265 msg_Err(obj
, "cannot set TLS session credentials: %s",
266 gnutls_strerror(val
));
272 gnutls_datum_t
*protv
= NULL
;
275 while (*alpn
!= NULL
)
277 gnutls_datum_t
*n
= realloc(protv
, sizeof (*protv
) * (protc
+ 1));
278 if (unlikely(n
== NULL
))
285 protv
[protc
].data
= (void *)*alpn
;
286 protv
[protc
].size
= strlen(*alpn
);
291 val
= gnutls_alpn_set_protocols (session
, protv
, protc
, 0);
295 gnutls_transport_set_ptr(session
, sock
);
296 gnutls_transport_set_vec_push_function(session
, vlc_gnutls_writev
);
297 gnutls_transport_set_pull_function(session
, vlc_gnutls_read
);
299 priv
->session
= session
;
302 vlc_tls_t
*tls
= &priv
->tls
;
304 tls
->ops
= &gnutls_ops
;
308 gnutls_deinit (session
);
314 * Starts or continues the TLS handshake.
316 * @return -1 on fatal error, 0 on successful handshake completion,
317 * 1 if more would-be blocking recv is needed,
318 * 2 if more would-be blocking send is required.
320 static int gnutls_Handshake(vlc_tls_t
*tls
, char **restrict alp
)
322 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
323 vlc_object_t
*obj
= priv
->obj
;
324 gnutls_session_t session
= priv
->session
;
325 int val
= gnutls_handshake(session
);
327 if (gnutls_error_is_fatal(val
))
329 msg_Err(obj
, "TLS handshake error: %s", gnutls_strerror(val
));
335 case GNUTLS_E_SUCCESS
:
338 case GNUTLS_E_INTERRUPTED
:
339 /* I/O event: return to caller's poll() loop */
340 return 1 + gnutls_record_get_direction(session
);
342 msg_Warn(obj
, "TLS handshake error: %s", gnutls_strerror(val
));
346 msg_Dbg(obj
, "TLS handshake complete");
347 #if (GNUTLS_VERSION_NUMBER >= 0x030500)
348 /* intentionally left blank */;
350 unsigned flags
= gnutls_session_get_flags(session
);
352 if (flags
& GNUTLS_SFLAGS_SAFE_RENEGOTIATION
)
353 msg_Dbg(obj
, " - safe renegotiation (RFC5746) enabled");
354 if (flags
& GNUTLS_SFLAGS_EXT_MASTER_SECRET
)
355 msg_Dbg(obj
, " - extended master secret (RFC7627) enabled");
356 if (flags
& GNUTLS_SFLAGS_ETM
)
357 msg_Dbg(obj
, " - encrypt then MAC (RFC7366) enabled");
358 if (flags
& GNUTLS_SFLAGS_FALSE_START
)
359 msg_Dbg(obj
, " - false start (RFC7918) enabled");
364 gnutls_datum_t datum
;
366 val
= gnutls_alpn_get_selected_protocol (session
, &datum
);
369 if (memchr (datum
.data
, 0, datum
.size
) != NULL
)
370 return -1; /* Other end is doing something fishy?! */
372 *alp
= strndup ((char *)datum
.data
, datum
.size
);
373 if (unlikely(*alp
== NULL
))
382 static vlc_tls_t
*gnutls_ClientSessionOpen(vlc_tls_client_t
*crd
,
383 vlc_tls_t
*sk
, const char *hostname
,
384 const char *const *alpn
)
386 vlc_tls_gnutls_t
*priv
= gnutls_SessionOpen(VLC_OBJECT(crd
), GNUTLS_CLIENT
,
391 gnutls_session_t session
= priv
->session
;
393 /* minimum DH prime bits */
394 gnutls_dh_set_prime_bits (session
, 1024);
396 if (likely(hostname
!= NULL
))
397 /* fill Server Name Indication */
398 gnutls_server_name_set (session
, GNUTLS_NAME_DNS
,
399 hostname
, strlen (hostname
));
404 static int gnutls_ClientHandshake(vlc_tls_t
*tls
,
405 const char *host
, const char *service
,
408 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
409 vlc_object_t
*obj
= priv
->obj
;
411 int val
= gnutls_Handshake(tls
, alp
);
415 /* certificates chain verification */
416 gnutls_session_t session
= priv
->session
;
419 val
= gnutls_certificate_verify_peers3 (session
, host
, &status
);
422 msg_Err(obj
, "Certificate verification error: %s",
423 gnutls_strerror(val
));
427 if (status
== 0) /* Good certificate */
430 /* Bad certificate */
433 if (gnutls_certificate_verification_status_print(status
,
434 gnutls_certificate_type_get (session
), &desc
, 0) == 0)
436 msg_Err(obj
, "Certificate verification failure: %s", desc
.data
);
437 gnutls_free (desc
.data
);
440 status
&= ~GNUTLS_CERT_INVALID
; /* always set / catch-all error */
441 status
&= ~GNUTLS_CERT_SIGNER_NOT_FOUND
; /* unknown CA */
442 status
&= ~GNUTLS_CERT_UNEXPECTED_OWNER
; /* mismatched hostname */
444 if (status
!= 0 || host
== NULL
)
445 goto error
; /* Really bad certificate */
447 /* Look up mismatching certificate in store */
448 const gnutls_datum_t
*datum
;
451 datum
= gnutls_certificate_get_peers (session
, &count
);
452 if (datum
== NULL
|| count
== 0)
454 msg_Err(obj
, "Peer certificate not available");
458 msg_Dbg(obj
, "%u certificate(s) in the list", count
);
459 val
= gnutls_verify_stored_pubkey (NULL
, NULL
, host
, service
,
460 GNUTLS_CRT_X509
, datum
, 0);
465 msg_Dbg(obj
, "certificate key match for %s", host
);
467 case GNUTLS_E_NO_CERTIFICATE_FOUND
:
468 msg_Dbg(obj
, "no known certificates for %s", host
);
469 msg
= N_("However, the security certificate presented by the "
470 "server is unknown and could not be authenticated by any "
471 "trusted Certificate Authority.");
473 case GNUTLS_E_CERTIFICATE_KEY_MISMATCH
:
474 msg_Dbg(obj
, "certificate keys mismatch for %s", host
);
475 msg
= N_("However, the security certificate presented by the "
476 "server changed since the previous visit and was not "
477 "authenticated by any trusted Certificate Authority.");
480 msg_Err(obj
, "certificate key match error for %s: %s", host
,
481 gnutls_strerror(val
));
485 if (vlc_dialog_wait_question(obj
, VLC_DIALOG_QUESTION_WARNING
,
486 _("Abort"), _("View certificate"), NULL
,
488 _("You attempted to reach %s. %s\n"
489 "This problem may be stem from an attempt to breach your security, "
490 "compromise your privacy, or a configuration error.\n\n"
491 "If in doubt, abort now.\n"), host
, vlc_gettext(msg
)) != 1)
494 gnutls_x509_crt_t cert
;
496 if (gnutls_x509_crt_init (&cert
))
498 if (gnutls_x509_crt_import (cert
, datum
, GNUTLS_X509_FMT_DER
)
499 || gnutls_x509_crt_print (cert
, GNUTLS_CRT_PRINT_ONELINE
, &desc
))
501 gnutls_x509_crt_deinit (cert
);
504 gnutls_x509_crt_deinit (cert
);
506 val
= vlc_dialog_wait_question(obj
, VLC_DIALOG_QUESTION_WARNING
,
507 _("Abort"), _("Accept 24 hours"), _("Accept permanently"),
509 _("This is the certificate presented by %s:\n%s\n\n"
510 "If in doubt, abort now.\n"), host
, desc
.data
);
511 gnutls_free (desc
.data
);
518 expiry
+= 24 * 60 * 60;
521 val
= gnutls_store_pubkey (NULL
, NULL
, host
, service
,
522 GNUTLS_CRT_X509
, datum
, expiry
, 0);
524 msg_Err(obj
, "cannot store X.509 certificate: %s",
525 gnutls_strerror (val
));
538 static void gnutls_ClientDestroy(vlc_tls_client_t
*crd
)
540 gnutls_certificate_credentials_t x509
= crd
->sys
;
542 gnutls_certificate_free_credentials(x509
);
545 static const struct vlc_tls_client_operations gnutls_ClientOps
=
547 .open
= gnutls_ClientSessionOpen
,
548 .handshake
= gnutls_ClientHandshake
,
549 .destroy
= gnutls_ClientDestroy
,
553 * Initializes a client-side TLS credentials.
555 static int OpenClient(vlc_tls_client_t
*crd
)
557 gnutls_certificate_credentials_t x509
;
559 gnutls_Banner(VLC_OBJECT(crd
));
561 int val
= gnutls_certificate_allocate_credentials (&x509
);
564 msg_Err (crd
, "cannot allocate credentials: %s",
565 gnutls_strerror (val
));
569 if (var_InheritBool(crd
, "gnutls-system-trust"))
571 val
= gnutls_certificate_set_x509_system_trust(x509
);
573 msg_Err(crd
, "cannot load trusted Certificate Authorities "
574 "from %s: %s", "system", gnutls_strerror(val
));
576 msg_Dbg(crd
, "loaded %d trusted CAs from %s", val
, "system");
579 char *dir
= var_InheritString(crd
, "gnutls-dir-trust");
582 val
= gnutls_certificate_set_x509_trust_dir(x509
, dir
,
583 GNUTLS_X509_FMT_PEM
);
585 msg_Err(crd
, "cannot load trusted Certificate Authorities "
586 "from %s: %s", dir
, gnutls_strerror(val
));
588 msg_Dbg(crd
, "loaded %d trusted CAs from %s", val
, dir
);
592 gnutls_certificate_set_verify_flags (x509
,
593 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT
);
595 crd
->ops
= &gnutls_ClientOps
;
602 * Server-side TLS credentials private data
604 typedef struct vlc_tls_creds_sys
606 gnutls_certificate_credentials_t x509_cred
;
607 gnutls_dh_params_t dh_params
;
608 } vlc_tls_creds_sys_t
;
611 * Initializes a server-side TLS session.
613 static vlc_tls_t
*gnutls_ServerSessionOpen(vlc_tls_server_t
*crd
,
615 const char *const *alpn
)
617 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
618 vlc_tls_gnutls_t
*priv
= gnutls_SessionOpen(VLC_OBJECT(crd
), GNUTLS_SERVER
,
619 sys
->x509_cred
, sk
, alpn
);
620 return (priv
!= NULL
) ? &priv
->tls
: NULL
;
623 static void gnutls_ServerDestroy(vlc_tls_server_t
*crd
)
625 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
627 /* all sessions depending on the server are now deinitialized */
628 gnutls_certificate_free_credentials(sys
->x509_cred
);
629 gnutls_dh_params_deinit(sys
->dh_params
);
633 static const struct vlc_tls_server_operations gnutls_ServerOps
=
635 .open
= gnutls_ServerSessionOpen
,
636 .handshake
= gnutls_Handshake
,
637 .destroy
= gnutls_ServerDestroy
,
641 * Allocates a whole server's TLS credentials.
643 static int OpenServer(vlc_tls_server_t
*crd
, const char *cert
, const char *key
)
645 gnutls_Banner(VLC_OBJECT(crd
));
647 vlc_tls_creds_sys_t
*sys
= malloc (sizeof (*sys
));
648 if (unlikely(sys
== NULL
))
651 /* Sets server's credentials */
652 int val
= gnutls_certificate_allocate_credentials (&sys
->x509_cred
);
655 msg_Err (crd
, "cannot allocate credentials: %s",
656 gnutls_strerror (val
));
661 block_t
*certblock
= block_FilePath(cert
, false);
662 if (certblock
== NULL
)
664 msg_Err (crd
, "cannot read certificate chain from %s: %s", cert
,
665 vlc_strerror_c(errno
));
669 block_t
*keyblock
= block_FilePath(key
, false);
670 if (keyblock
== NULL
)
672 msg_Err (crd
, "cannot read private key from %s: %s", key
,
673 vlc_strerror_c(errno
));
674 block_Release (certblock
);
678 gnutls_datum_t pub
= {
679 .data
= certblock
->p_buffer
,
680 .size
= certblock
->i_buffer
,
682 .data
= keyblock
->p_buffer
,
683 .size
= keyblock
->i_buffer
,
686 val
= gnutls_certificate_set_x509_key_mem (sys
->x509_cred
, &pub
, &priv
,
687 GNUTLS_X509_FMT_PEM
);
688 block_Release (keyblock
);
689 block_Release (certblock
);
692 msg_Err (crd
, "cannot load X.509 key: %s", gnutls_strerror (val
));
697 * - regenerate these regularly
698 * - support other cipher suites
700 val
= gnutls_dh_params_init (&sys
->dh_params
);
703 gnutls_sec_param_t sec
= GNUTLS_SEC_PARAM_MEDIUM
;
704 unsigned bits
= gnutls_sec_param_to_pk_bits (GNUTLS_PK_DH
, sec
);
706 msg_Dbg (crd
, "generating Diffie-Hellman %u-bits parameters...", bits
);
707 val
= gnutls_dh_params_generate2 (sys
->dh_params
, bits
);
709 gnutls_certificate_set_dh_params (sys
->x509_cred
,
714 msg_Err (crd
, "cannot initialize DHE cipher suites: %s",
715 gnutls_strerror (val
));
718 msg_Dbg (crd
, "ciphers parameters loaded");
720 crd
->ops
= &gnutls_ServerOps
;
725 gnutls_certificate_free_credentials (sys
->x509_cred
);
731 #define SYSTEM_TRUST_TEXT N_("Use system trust database")
732 #define SYSTEM_TRUST_LONGTEXT N_( \
733 "Trust the root certificates of Certificate Authorities stored in " \
734 "the operating system trust database to authenticate TLS sessions.")
736 #define DIR_TRUST_TEXT N_("Trust directory")
737 #define DIR_TRUST_LONGTEXT N_( \
738 "Trust the root certificates of Certificate Authorities stored in " \
739 "the specified directory to authenticate TLS sessions.")
741 #define PRIORITIES_TEXT N_("TLS cipher priorities")
742 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
743 "hash functions and compression methods can be selected. " \
744 "Refer to GNU TLS documentation for detailed syntax.")
745 static const char *const priorities_values
[] = {
752 static const char *const priorities_text
[] = {
753 N_("Performance (prioritize faster ciphers)"),
755 N_("Secure 128-bits (exclude 256-bits ciphers)"),
756 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
757 N_("Export (include insecure ciphers)"),
761 set_shortname( "GNU TLS" )
762 set_description( N_("GNU TLS transport layer security") )
763 set_capability( "tls client", 1 )
764 set_callbacks(OpenClient
, NULL
)
765 set_category( CAT_ADVANCED
)
766 set_subcategory( SUBCAT_ADVANCED_NETWORK
)
767 add_bool("gnutls-system-trust", true, SYSTEM_TRUST_TEXT
,
768 SYSTEM_TRUST_LONGTEXT
, true)
769 add_string("gnutls-dir-trust", NULL
, DIR_TRUST_TEXT
,
770 DIR_TRUST_TEXT
, true)
771 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT
,
772 PRIORITIES_LONGTEXT
, false)
773 change_string_list (priorities_values
, priorities_text
)
776 set_description( N_("GNU TLS server") )
777 set_capability( "tls server", 1 )
778 set_category( CAT_ADVANCED
)
779 set_subcategory( SUBCAT_ADVANCED_NETWORK
)
780 set_callbacks(OpenServer
, NULL
)