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 int gnutls_Init (vlc_object_t
*obj
)
54 const char *version
= gnutls_check_version ("3.3.0");
57 msg_Err (obj
, "unsupported GnuTLS version");
60 msg_Dbg (obj
, "using GnuTLS version %s", version
);
64 static int gnutls_Error(vlc_tls_gnutls_t
*priv
, int val
)
70 WSASetLastError (WSAEWOULDBLOCK
);
75 case GNUTLS_E_INTERRUPTED
:
77 WSASetLastError (WSAEINTR
);
83 msg_Err(priv
->obj
, "%s", gnutls_strerror (val
));
85 if (!gnutls_error_is_fatal (val
))
86 msg_Err(priv
->obj
, "Error above should be handled");
89 WSASetLastError (WSAECONNRESET
);
96 static ssize_t
vlc_gnutls_read(gnutls_transport_ptr_t ptr
, void *buf
,
99 vlc_tls_t
*sock
= ptr
;
105 return sock
->readv(sock
, &iov
, 1);
108 static ssize_t
vlc_gnutls_writev(gnutls_transport_ptr_t ptr
,
109 const giovec_t
*giov
, int iovcnt
)
112 const long iovmax
= IOV_MAX
;
114 const long iovmax
= sysconf(_SC_IOV_MAX
);
116 if (unlikely(iovcnt
> iovmax
))
121 if (unlikely(iovcnt
== 0))
124 vlc_tls_t
*sock
= ptr
;
125 struct iovec iov
[iovcnt
];
127 for (int i
= 0; i
< iovcnt
; i
++)
129 iov
[i
].iov_base
= giov
[i
].iov_base
;
130 iov
[i
].iov_len
= giov
[i
].iov_len
;
133 return sock
->writev(sock
, iov
, iovcnt
);
136 static int gnutls_GetFD(vlc_tls_t
*tls
)
138 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
139 vlc_tls_t
*sock
= gnutls_transport_get_ptr(priv
->session
);
141 return vlc_tls_GetFD(sock
);
144 static ssize_t
gnutls_Recv(vlc_tls_t
*tls
, struct iovec
*iov
, unsigned count
)
146 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
147 gnutls_session_t session
= priv
->session
;
152 ssize_t val
= gnutls_record_recv(session
, iov
->iov_base
, iov
->iov_len
);
154 return rcvd
? (ssize_t
)rcvd
: gnutls_Error(priv
, val
);
158 if ((size_t)val
< iov
->iov_len
)
168 static ssize_t
gnutls_Send (vlc_tls_t
*tls
, const struct iovec
*iov
,
171 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
172 gnutls_session_t session
= priv
->session
;
175 if (!gnutls_record_check_corked(session
))
177 gnutls_record_cork(session
);
181 val
= gnutls_record_send(session
, iov
->iov_base
, iov
->iov_len
);
182 if (val
< (ssize_t
)iov
->iov_len
)
190 val
= gnutls_record_uncork(session
, 0);
191 return (val
< 0) ? gnutls_Error(priv
, val
) : val
;
194 static int gnutls_Shutdown(vlc_tls_t
*tls
, bool duplex
)
196 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
197 gnutls_session_t session
= priv
->session
;
200 /* Flush any pending data */
201 val
= gnutls_record_uncork(session
, 0);
203 return gnutls_Error(priv
, val
);
205 val
= gnutls_bye(session
, duplex
? GNUTLS_SHUT_RDWR
: GNUTLS_SHUT_WR
);
207 return gnutls_Error(priv
, val
);
212 static void gnutls_Close (vlc_tls_t
*tls
)
214 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
216 gnutls_deinit(priv
->session
);
220 static vlc_tls_gnutls_t
*gnutls_SessionOpen(vlc_tls_creds_t
*creds
, int type
,
221 gnutls_certificate_credentials_t x509
,
223 const char *const *alpn
)
225 vlc_tls_gnutls_t
*priv
= malloc(sizeof (*priv
));
226 if (unlikely(priv
== NULL
))
229 gnutls_session_t session
;
233 type
|= GNUTLS_NONBLOCK
;
234 #if (GNUTLS_VERSION_NUMBER >= 0x030500)
235 type
|= GNUTLS_ENABLE_FALSE_START
;
238 val
= gnutls_init(&session
, type
);
241 msg_Err(creds
, "cannot initialize TLS session: %s",
242 gnutls_strerror(val
));
247 char *priorities
= var_InheritString(creds
, "gnutls-priorities");
248 if (unlikely(priorities
== NULL
))
251 val
= gnutls_priority_set_direct (session
, priorities
, &errp
);
253 msg_Err(creds
, "cannot set TLS priorities \"%s\": %s", errp
,
254 gnutls_strerror(val
));
259 val
= gnutls_credentials_set (session
, GNUTLS_CRD_CERTIFICATE
, x509
);
262 msg_Err(creds
, "cannot set TLS session credentials: %s",
263 gnutls_strerror(val
));
269 gnutls_datum_t
*protv
= NULL
;
272 while (*alpn
!= NULL
)
274 gnutls_datum_t
*n
= realloc(protv
, sizeof (*protv
) * (protc
+ 1));
275 if (unlikely(n
== NULL
))
282 protv
[protc
].data
= (void *)*alpn
;
283 protv
[protc
].size
= strlen(*alpn
);
288 val
= gnutls_alpn_set_protocols (session
, protv
, protc
, 0);
292 gnutls_transport_set_ptr(session
, sock
);
293 gnutls_transport_set_vec_push_function(session
, vlc_gnutls_writev
);
294 gnutls_transport_set_pull_function(session
, vlc_gnutls_read
);
296 priv
->session
= session
;
297 priv
->obj
= VLC_OBJECT(creds
);
299 vlc_tls_t
*tls
= &priv
->tls
;
301 tls
->get_fd
= gnutls_GetFD
;
302 tls
->readv
= gnutls_Recv
;
303 tls
->writev
= gnutls_Send
;
304 tls
->shutdown
= gnutls_Shutdown
;
305 tls
->close
= gnutls_Close
;
309 gnutls_deinit (session
);
315 * Starts or continues the TLS handshake.
317 * @return -1 on fatal error, 0 on successful handshake completion,
318 * 1 if more would-be blocking recv is needed,
319 * 2 if more would-be blocking send is required.
321 static int gnutls_ContinueHandshake(vlc_tls_creds_t
*crd
,
322 vlc_tls_gnutls_t
*priv
,
325 gnutls_session_t session
= priv
->session
;
333 val
= gnutls_handshake (session
);
334 msg_Dbg(crd
, "TLS handshake: %s", gnutls_strerror (val
));
338 case GNUTLS_E_SUCCESS
:
341 case GNUTLS_E_INTERRUPTED
:
342 /* I/O event: return to caller's poll() loop */
343 return 1 + gnutls_record_get_direction (session
);
346 while (!gnutls_error_is_fatal (val
));
349 msg_Dbg(crd
, "Winsock error %d", WSAGetLastError ());
351 msg_Err(crd
, "TLS handshake error: %s", gnutls_strerror (val
));
355 #if (GNUTLS_VERSION_NUMBER >= 0x030500)
356 /* intentionally left blank */;
358 unsigned flags
= gnutls_session_get_flags(session
);
360 if (flags
& GNUTLS_SFLAGS_SAFE_RENEGOTIATION
)
361 msg_Dbg(crd
, " - safe renegotiation (RFC5746) enabled");
362 if (flags
& GNUTLS_SFLAGS_EXT_MASTER_SECRET
)
363 msg_Dbg(crd
, " - extended master secret (RFC7627) enabled");
364 if (flags
& GNUTLS_SFLAGS_ETM
)
365 msg_Dbg(crd
, " - encrypt then MAC (RFC7366) enabled");
366 if (flags
& GNUTLS_SFLAGS_FALSE_START
)
367 msg_Dbg(crd
, " - false start (RFC7918) enabled");
372 gnutls_datum_t datum
;
374 val
= gnutls_alpn_get_selected_protocol (session
, &datum
);
377 if (memchr (datum
.data
, 0, datum
.size
) != NULL
)
378 return -1; /* Other end is doing something fishy?! */
380 *alp
= strndup ((char *)datum
.data
, datum
.size
);
381 if (unlikely(*alp
== NULL
))
390 static vlc_tls_t
*gnutls_ClientSessionOpen(vlc_tls_creds_t
*crd
,
391 vlc_tls_t
*sk
, const char *hostname
,
392 const char *const *alpn
)
394 vlc_tls_gnutls_t
*priv
;
396 priv
= gnutls_SessionOpen(crd
, GNUTLS_CLIENT
, crd
->sys
, sk
, alpn
);
400 gnutls_session_t session
= priv
->session
;
402 /* minimum DH prime bits */
403 gnutls_dh_set_prime_bits (session
, 1024);
405 if (likely(hostname
!= NULL
))
406 /* fill Server Name Indication */
407 gnutls_server_name_set (session
, GNUTLS_NAME_DNS
,
408 hostname
, strlen (hostname
));
413 static int gnutls_ClientHandshake(vlc_tls_creds_t
*creds
, vlc_tls_t
*tls
,
414 const char *host
, const char *service
,
417 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
419 int val
= gnutls_ContinueHandshake(creds
, priv
, alp
);
423 /* certificates chain verification */
424 gnutls_session_t session
= priv
->session
;
427 val
= gnutls_certificate_verify_peers3 (session
, host
, &status
);
430 msg_Err(creds
, "Certificate verification error: %s",
431 gnutls_strerror(val
));
435 if (status
== 0) /* Good certificate */
438 /* Bad certificate */
441 if (gnutls_certificate_verification_status_print(status
,
442 gnutls_certificate_type_get (session
), &desc
, 0) == 0)
444 msg_Err(creds
, "Certificate verification failure: %s", desc
.data
);
445 gnutls_free (desc
.data
);
448 status
&= ~GNUTLS_CERT_INVALID
; /* always set / catch-all error */
449 status
&= ~GNUTLS_CERT_SIGNER_NOT_FOUND
; /* unknown CA */
450 status
&= ~GNUTLS_CERT_UNEXPECTED_OWNER
; /* mismatched hostname */
452 if (status
!= 0 || host
== NULL
)
453 goto error
; /* Really bad certificate */
455 /* Look up mismatching certificate in store */
456 const gnutls_datum_t
*datum
;
459 datum
= gnutls_certificate_get_peers (session
, &count
);
460 if (datum
== NULL
|| count
== 0)
462 msg_Err(creds
, "Peer certificate not available");
466 msg_Dbg(creds
, "%u certificate(s) in the list", count
);
467 val
= gnutls_verify_stored_pubkey (NULL
, NULL
, host
, service
,
468 GNUTLS_CRT_X509
, datum
, 0);
473 msg_Dbg(creds
, "certificate key match for %s", host
);
475 case GNUTLS_E_NO_CERTIFICATE_FOUND
:
476 msg_Dbg(creds
, "no known certificates for %s", host
);
477 msg
= N_("However, the security certificate presented by the "
478 "server is unknown and could not be authenticated by any "
479 "trusted Certificate Authority.");
481 case GNUTLS_E_CERTIFICATE_KEY_MISMATCH
:
482 msg_Dbg(creds
, "certificate keys mismatch for %s", host
);
483 msg
= N_("However, the security certificate presented by the "
484 "server changed since the previous visit and was not "
485 "authenticated by any trusted Certificate Authority. ");
488 msg_Err(creds
, "certificate key match error for %s: %s", host
,
489 gnutls_strerror(val
));
493 if (vlc_dialog_wait_question(creds
, VLC_DIALOG_QUESTION_WARNING
,
494 _("Abort"), _("View certificate"), NULL
,
496 _("You attempted to reach %s. %s\n"
497 "This problem may be stem from an attempt to breach your security, "
498 "compromise your privacy, or a configuration error.\n\n"
499 "If in doubt, abort now.\n"), host
, vlc_gettext(msg
)) != 1)
502 gnutls_x509_crt_t cert
;
504 if (gnutls_x509_crt_init (&cert
))
506 if (gnutls_x509_crt_import (cert
, datum
, GNUTLS_X509_FMT_DER
)
507 || gnutls_x509_crt_print (cert
, GNUTLS_CRT_PRINT_ONELINE
, &desc
))
509 gnutls_x509_crt_deinit (cert
);
512 gnutls_x509_crt_deinit (cert
);
514 val
= vlc_dialog_wait_question(creds
, VLC_DIALOG_QUESTION_WARNING
,
515 _("Abort"), _("Accept 24 hours"), _("Accept permanently"),
517 _("This is the certificate presented by %s:\n%s\n\n"
518 "If in doubt, abort now.\n"), host
, desc
.data
);
519 gnutls_free (desc
.data
);
526 expiry
+= 24 * 60 * 60;
529 val
= gnutls_store_pubkey (NULL
, NULL
, host
, service
,
530 GNUTLS_CRT_X509
, datum
, expiry
, 0);
532 msg_Err(creds
, "cannot store X.509 certificate: %s",
533 gnutls_strerror (val
));
547 * Initializes a client-side TLS credentials.
549 static int OpenClient (vlc_tls_creds_t
*crd
)
551 gnutls_certificate_credentials_t x509
;
553 if (gnutls_Init (VLC_OBJECT(crd
)))
556 int val
= gnutls_certificate_allocate_credentials (&x509
);
559 msg_Err (crd
, "cannot allocate credentials: %s",
560 gnutls_strerror (val
));
564 if (var_InheritBool(crd
, "gnutls-system-trust"))
566 val
= gnutls_certificate_set_x509_system_trust(x509
);
568 msg_Err(crd
, "cannot load trusted Certificate Authorities "
569 "from %s: %s", "system", gnutls_strerror(val
));
571 msg_Dbg(crd
, "loaded %d trusted CAs from %s", val
, "system");
574 char *dir
= var_InheritString(crd
, "gnutls-dir-trust");
577 val
= gnutls_certificate_set_x509_trust_dir(x509
, dir
,
578 GNUTLS_X509_FMT_PEM
);
580 msg_Err(crd
, "cannot load trusted Certificate Authorities "
581 "from %s: %s", dir
, gnutls_strerror(val
));
583 msg_Dbg(crd
, "loaded %d trusted CAs from %s", val
, dir
);
587 gnutls_certificate_set_verify_flags (x509
,
588 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT
);
591 crd
->open
= gnutls_ClientSessionOpen
;
592 crd
->handshake
= gnutls_ClientHandshake
;
597 static void CloseClient (vlc_tls_creds_t
*crd
)
599 gnutls_certificate_credentials_t x509
= crd
->sys
;
601 gnutls_certificate_free_credentials (x509
);
606 * Server-side TLS credentials private data
608 typedef struct vlc_tls_creds_sys
610 gnutls_certificate_credentials_t x509_cred
;
611 gnutls_dh_params_t dh_params
;
612 } vlc_tls_creds_sys_t
;
615 * Initializes a server-side TLS session.
617 static vlc_tls_t
*gnutls_ServerSessionOpen(vlc_tls_creds_t
*crd
,
618 vlc_tls_t
*sk
, const char *hostname
,
619 const char *const *alpn
)
621 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
622 vlc_tls_gnutls_t
*priv
;
624 assert (hostname
== NULL
);
625 priv
= gnutls_SessionOpen(crd
, GNUTLS_SERVER
, sys
->x509_cred
, sk
, alpn
);
626 return (priv
!= NULL
) ? &priv
->tls
: NULL
;
629 static int gnutls_ServerHandshake(vlc_tls_creds_t
*crd
, vlc_tls_t
*tls
,
630 const char *host
, const char *service
,
633 vlc_tls_gnutls_t
*priv
= (vlc_tls_gnutls_t
*)tls
;
635 (void) host
; (void) service
;
636 return gnutls_ContinueHandshake(crd
, priv
, alp
);
640 * Allocates a whole server's TLS credentials.
642 static int OpenServer (vlc_tls_creds_t
*crd
, const char *cert
, const char *key
)
646 if (gnutls_Init (VLC_OBJECT(crd
)))
649 vlc_tls_creds_sys_t
*sys
= malloc (sizeof (*sys
));
650 if (unlikely(sys
== NULL
))
653 /* Sets server's credentials */
654 val
= gnutls_certificate_allocate_credentials (&sys
->x509_cred
);
657 msg_Err (crd
, "cannot allocate credentials: %s",
658 gnutls_strerror (val
));
663 block_t
*certblock
= block_FilePath(cert
, false);
664 if (certblock
== NULL
)
666 msg_Err (crd
, "cannot read certificate chain from %s: %s", cert
,
667 vlc_strerror_c(errno
));
671 block_t
*keyblock
= block_FilePath(key
, false);
672 if (keyblock
== NULL
)
674 msg_Err (crd
, "cannot read private key from %s: %s", key
,
675 vlc_strerror_c(errno
));
676 block_Release (certblock
);
680 gnutls_datum_t pub
= {
681 .data
= certblock
->p_buffer
,
682 .size
= certblock
->i_buffer
,
684 .data
= keyblock
->p_buffer
,
685 .size
= keyblock
->i_buffer
,
688 val
= gnutls_certificate_set_x509_key_mem (sys
->x509_cred
, &pub
, &priv
,
689 GNUTLS_X509_FMT_PEM
);
690 block_Release (keyblock
);
691 block_Release (certblock
);
694 msg_Err (crd
, "cannot load X.509 key: %s", gnutls_strerror (val
));
699 * - regenerate these regularly
700 * - support other cipher suites
702 val
= gnutls_dh_params_init (&sys
->dh_params
);
705 gnutls_sec_param_t sec
= GNUTLS_SEC_PARAM_MEDIUM
;
706 unsigned bits
= gnutls_sec_param_to_pk_bits (GNUTLS_PK_DH
, sec
);
708 msg_Dbg (crd
, "generating Diffie-Hellman %u-bits parameters...", bits
);
709 val
= gnutls_dh_params_generate2 (sys
->dh_params
, bits
);
711 gnutls_certificate_set_dh_params (sys
->x509_cred
,
716 msg_Err (crd
, "cannot initialize DHE cipher suites: %s",
717 gnutls_strerror (val
));
720 msg_Dbg (crd
, "ciphers parameters loaded");
723 crd
->open
= gnutls_ServerSessionOpen
;
724 crd
->handshake
= gnutls_ServerHandshake
;
729 gnutls_certificate_free_credentials (sys
->x509_cred
);
735 * Destroys a TLS server object.
737 static void CloseServer (vlc_tls_creds_t
*crd
)
739 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
741 /* all sessions depending on the server are now deinitialized */
742 gnutls_certificate_free_credentials (sys
->x509_cred
);
743 gnutls_dh_params_deinit (sys
->dh_params
);
748 #define SYSTEM_TRUST_TEXT N_("Use system trust database")
749 #define SYSTEM_TRUST_LONGTEXT N_( \
750 "Trust the root certificates of Certificate Authorities stored in " \
751 "the operating system trust database to authenticate TLS sessions.")
753 #define DIR_TRUST_TEXT N_("Trust directory")
754 #define DIR_TRUST_LONGTEXT N_( \
755 "Trust the root certificates of Certificate Authorities stored in " \
756 "the specified directory to authenticate TLS sessions.")
758 #define PRIORITIES_TEXT N_("TLS cipher priorities")
759 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
760 "hash functions and compression methods can be selected. " \
761 "Refer to GNU TLS documentation for detailed syntax.")
762 static const char *const priorities_values
[] = {
769 static const char *const priorities_text
[] = {
770 N_("Performance (prioritize faster ciphers)"),
772 N_("Secure 128-bits (exclude 256-bits ciphers)"),
773 N_("Secure 256-bits (prioritize 256-bits ciphers)"),
774 N_("Export (include insecure ciphers)"),
778 set_shortname( "GNU TLS" )
779 set_description( N_("GNU TLS transport layer security") )
780 set_capability( "tls client", 1 )
781 set_callbacks( OpenClient
, CloseClient
)
782 set_category( CAT_ADVANCED
)
783 set_subcategory( SUBCAT_ADVANCED_NETWORK
)
784 add_bool("gnutls-system-trust", true, SYSTEM_TRUST_TEXT
,
785 SYSTEM_TRUST_LONGTEXT
, true)
786 add_string("gnutls-dir-trust", NULL
, DIR_TRUST_TEXT
,
787 DIR_TRUST_TEXT
, true)
788 add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT
,
789 PRIORITIES_LONGTEXT
, false)
790 change_string_list (priorities_values
, priorities_text
)
793 set_description( N_("GNU TLS server") )
794 set_capability( "tls server", 1 )
795 set_category( CAT_ADVANCED
)
796 set_subcategory( SUBCAT_ADVANCED_NETWORK
)
797 set_callbacks( OpenServer
, CloseServer
)