]> git.sesse.net Git - vlc/blob - modules/misc/gnutls.c
tls: add service parameter for handshake
[vlc] / modules / misc / gnutls.c
1 /*****************************************************************************
2  * gnutls.c
3  *****************************************************************************
4  * Copyright (C) 2004-2012 Rémi Denis-Courmont
5  *
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.
10  *
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.
15  *
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  *****************************************************************************/
20
21 /*****************************************************************************
22  * Preamble
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <errno.h>
30 #include <assert.h>
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_tls.h>
35 #include <vlc_block.h>
36 #include <vlc_dialog.h>
37
38 #include <gnutls/gnutls.h>
39 #include <gnutls/x509.h>
40 #if (GNUTLS_VERSION_NUMBER < 0x030014)
41 # define gnutls_certificate_set_x509_system_trust(c) \
42     (c, GNUTLS_E_UNIMPLEMENTED_FEATURE)
43 #endif
44
45 #include "dhparams.h"
46
47 /*****************************************************************************
48  * Module descriptor
49  *****************************************************************************/
50 static int  OpenClient  (vlc_tls_creds_t *);
51 static void CloseClient (vlc_tls_creds_t *);
52 static int  OpenServer  (vlc_tls_creds_t *, const char *, const char *);
53 static void CloseServer (vlc_tls_creds_t *);
54
55 #define PRIORITIES_TEXT N_("TLS cipher priorities")
56 #define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
57     "hash functions and compression methods can be selected. " \
58     "Refer to GNU TLS documentation for detailed syntax.")
59 static const char *const priorities_values[] = {
60     "PERFORMANCE",
61     "NORMAL",
62     "SECURE128",
63     "SECURE256",
64     "EXPORT",
65 };
66 static const char *const priorities_text[] = {
67     N_("Performance (prioritize faster ciphers)"),
68     N_("Normal"),
69     N_("Secure 128-bits (exclude 256-bits ciphers)"),
70     N_("Secure 256-bits (prioritize 256-bits ciphers)"),
71     N_("Export (include insecure ciphers)"),
72 };
73
74 vlc_module_begin ()
75     set_shortname( "GNU TLS" )
76     set_description( N_("GNU TLS transport layer security") )
77     set_capability( "tls client", 1 )
78     set_callbacks( OpenClient, CloseClient )
79     set_category( CAT_ADVANCED )
80     set_subcategory( SUBCAT_ADVANCED_MISC )
81
82     add_submodule ()
83         set_description( N_("GNU TLS server") )
84         set_capability( "tls server", 1 )
85         set_category( CAT_ADVANCED )
86         set_subcategory( SUBCAT_ADVANCED_MISC )
87         set_callbacks( OpenServer, CloseServer )
88
89         add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
90                     PRIORITIES_LONGTEXT, false)
91             change_string_list (priorities_values, priorities_text)
92 vlc_module_end ()
93
94 static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;
95
96 /**
97  * Initializes GnuTLS with proper locking.
98  * @return VLC_SUCCESS on success, a VLC error code otherwise.
99  */
100 static int gnutls_Init (vlc_object_t *p_this)
101 {
102     int ret = VLC_EGENERIC;
103
104     vlc_mutex_lock (&gnutls_mutex);
105     if (gnutls_global_init ())
106     {
107         msg_Err (p_this, "cannot initialize GnuTLS");
108         goto error;
109     }
110
111     const char *psz_version = gnutls_check_version ("2.6.6");
112     if (psz_version == NULL)
113     {
114         msg_Err (p_this, "unsupported GnuTLS version");
115         gnutls_global_deinit ();
116         goto error;
117     }
118
119     msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
120     ret = VLC_SUCCESS;
121
122 error:
123     vlc_mutex_unlock (&gnutls_mutex);
124     return ret;
125 }
126
127
128 /**
129  * Deinitializes GnuTLS.
130  */
131 static void gnutls_Deinit (vlc_object_t *p_this)
132 {
133     vlc_mutex_lock (&gnutls_mutex);
134
135     gnutls_global_deinit ();
136     msg_Dbg (p_this, "GnuTLS deinitialized");
137     vlc_mutex_unlock (&gnutls_mutex);
138 }
139
140
141 static int gnutls_Error (vlc_object_t *obj, int val)
142 {
143     switch (val)
144     {
145         case GNUTLS_E_AGAIN:
146 #ifdef WIN32
147             WSASetLastError (WSAEWOULDBLOCK);
148 #else
149             errno = EAGAIN;
150 #endif
151             break;
152
153         case GNUTLS_E_INTERRUPTED:
154 #ifdef WIN32
155             WSASetLastError (WSAEINTR);
156 #else
157             errno = EINTR;
158 #endif
159             break;
160
161         default:
162             msg_Err (obj, "%s", gnutls_strerror (val));
163 #ifndef NDEBUG
164             if (!gnutls_error_is_fatal (val))
165                 msg_Err (obj, "Error above should be handled");
166 #endif
167 #ifdef WIN32
168             WSASetLastError (WSAECONNRESET);
169 #else
170             errno = ECONNRESET;
171 #endif
172     }
173     return -1;
174 }
175 #define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
176
177 struct vlc_tls_sys
178 {
179     gnutls_session_t session;
180     bool handshaked;
181 };
182
183
184 /**
185  * Sends data through a TLS session.
186  */
187 static int gnutls_Send (void *opaque, const void *buf, size_t length)
188 {
189     vlc_tls_t *session = opaque;
190     vlc_tls_sys_t *sys = session->sys;
191
192     int val = gnutls_record_send (sys->session, buf, length);
193     return (val < 0) ? gnutls_Error (session, val) : val;
194 }
195
196
197 /**
198  * Receives data through a TLS session.
199  */
200 static int gnutls_Recv (void *opaque, void *buf, size_t length)
201 {
202     vlc_tls_t *session = opaque;
203     vlc_tls_sys_t *sys = session->sys;
204
205     int val = gnutls_record_recv (sys->session, buf, length);
206     return (val < 0) ? gnutls_Error (session, val) : val;
207 }
208
209
210 /**
211  * Starts or continues the TLS handshake.
212  *
213  * @return -1 on fatal error, 0 on successful handshake completion,
214  * 1 if more would-be blocking recv is needed,
215  * 2 if more would-be blocking send is required.
216  */
217 static int gnutls_ContinueHandshake (vlc_tls_t *session, const char *host,
218                                      const char *service)
219 {
220     vlc_tls_sys_t *sys = session->sys;
221     int val;
222
223 #ifdef WIN32
224     WSASetLastError (0);
225 #endif
226     val = gnutls_handshake (sys->session);
227     if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
228         return 1 + gnutls_record_get_direction (sys->session);
229
230     if (val < 0)
231     {
232 #ifdef WIN32
233         msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
234 #endif
235         msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
236         return -1;
237     }
238
239     sys->handshaked = true;
240     (void) host; (void) service;
241     return 0;
242 }
243
244
245 /**
246  * Looks up certificate in known hosts data base.
247  * @return 0 on success, -1 on failure.
248  */
249 static int gnutls_CertSearch (vlc_tls_t *obj, const char *host,
250                               const gnutls_datum_t *restrict datum)
251 {
252     assert (host != NULL);
253
254     if (dialog_Question (obj, N_("Insecure site"),
255          N_("You attempted to reach %s, but security certificate presented by "
256             "the server could not be verified."
257             "This problem may be caused by a configuration error "
258             "on the server or by a serious breach of network security.\n\n"
259             "If in doubt, abort now.\n"),
260                          N_("Abort"), N_("View certificate"), NULL, host) != 2)
261          return -1;
262
263     gnutls_x509_crt_t cert;
264     gnutls_datum_t desc;
265
266     if (gnutls_x509_crt_init (&cert))
267         return -1;
268     if (gnutls_x509_crt_import (cert, datum, GNUTLS_X509_FMT_DER)
269      || gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &desc))
270     {
271         gnutls_x509_crt_deinit (cert);
272         return -1;
273     }
274     gnutls_x509_crt_deinit (cert);
275
276     int val = dialog_Question (obj, N_("Insecure site"),
277          N_("This is the certificate presented by %s:\n%s\n\n"
278             "If in doubt, abort now.\n"),
279                            N_("Abort"), N_("Proceed anyway"), NULL,
280                            host, desc.data);
281     gnutls_free (desc.data);
282
283     return (val == 2) ? 0 : -1;
284 }
285
286
287 static struct
288 {
289     int flag;
290     const char msg[43];
291     bool strict;
292 } cert_errs[] =
293 {
294     { GNUTLS_CERT_INVALID,
295         "Certificate could not be verified", false },
296     { GNUTLS_CERT_REVOKED,
297         "Certificate was revoked", true },
298     { GNUTLS_CERT_SIGNER_NOT_FOUND,
299         "Certificate's signer was not found", false },
300     { GNUTLS_CERT_SIGNER_NOT_CA,
301         "Certificate's signer is not a CA", true },
302     { GNUTLS_CERT_INSECURE_ALGORITHM,
303       "Insecure certificate signature algorithm", true },
304     { GNUTLS_CERT_NOT_ACTIVATED,
305         "Certificate is not yet activated", true },
306     { GNUTLS_CERT_EXPIRED,
307         "Certificate has expired", true },
308 };
309
310
311 static int gnutls_HandshakeAndValidate (vlc_tls_t *session, const char *host,
312                                         const char *service)
313 {
314     vlc_tls_sys_t *sys = session->sys;
315
316     int val = gnutls_ContinueHandshake (session, host, service);
317     if (val)
318         return val;
319
320     /* certificates chain verification */
321     unsigned status;
322
323     val = gnutls_certificate_verify_peers2 (sys->session, &status);
324     if (val)
325     {
326         msg_Err (session, "Certificate verification error: %s",
327                  gnutls_strerror (val));
328         return -1;
329     }
330
331     if (status)
332     {
333         msg_Err (session, "Certificate verification failure:");
334         for (size_t i = 0; i < sizeof (cert_errs) / sizeof (cert_errs[0]); i++)
335             if (status & cert_errs[i].flag)
336             {
337                 msg_Err (session, " * %s", cert_errs[i].msg);
338                 status &= ~cert_errs[i].flag;
339                 if (cert_errs[i].strict)
340                     val = -1;
341             }
342
343         if (status)
344         {
345             msg_Err (session, " * Unknown verification error 0x%04X", status);
346             val = -1;
347         }
348         status = -1;
349     }
350
351     /* certificate (host)name verification */
352     const gnutls_datum_t *data;
353     unsigned count;
354     data = gnutls_certificate_get_peers (sys->session, &count);
355     if (data == NULL || count == 0)
356     {
357         msg_Err (session, "Peer certificate not available");
358         return -1;
359     }
360     msg_Dbg (session, "%u certificate(s) in the list", count);
361
362     if (val || host == NULL)
363         return val;
364     if (status && gnutls_CertSearch (session, host, data))
365         return -1;
366
367     gnutls_x509_crt_t cert;
368     val = gnutls_x509_crt_init (&cert);
369     if (val)
370     {
371         msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
372         return -1;
373     }
374
375     val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
376     if (val)
377     {
378         msg_Err (session, "Certificate import error: %s",
379                  gnutls_strerror (val));
380         goto error;
381     }
382
383     val = !gnutls_x509_crt_check_hostname (cert, host);
384     if (val)
385     {
386         msg_Err (session, "Certificate does not match \"%s\"", host);
387         val = gnutls_CertSearch (session, host, data);
388     }
389 error:
390     gnutls_x509_crt_init (&cert);
391     return val ? -1 : 0;
392 }
393
394 static int
395 gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
396 {
397     char *priorities = var_InheritString (obj, "gnutls-priorities");
398     if (unlikely(priorities == NULL))
399         return VLC_ENOMEM;
400
401     const char *errp;
402     int val = gnutls_priority_set_direct (session, priorities, &errp);
403     if (val < 0)
404     {
405         msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
406                  gnutls_strerror (val));
407         val = VLC_EGENERIC;
408     }
409     else
410         val = VLC_SUCCESS;
411     free (priorities);
412     return val;
413 }
414
415
416 /**
417  * TLS credentials private data
418  */
419 struct vlc_tls_creds_sys
420 {
421     gnutls_certificate_credentials_t x509_cred;
422     gnutls_dh_params_t dh_params; /* XXX: used for server only */
423     int (*handshake) (vlc_tls_t *, const char *, const char *);
424         /* ^^ XXX: useful for server only */
425 };
426
427
428 /**
429  * Terminates TLS session and releases session data.
430  * You still have to close the socket yourself.
431  */
432 static void gnutls_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session)
433 {
434     vlc_tls_sys_t *sys = session->sys;
435
436     if (sys->handshaked)
437         gnutls_bye (sys->session, GNUTLS_SHUT_WR);
438     gnutls_deinit (sys->session);
439
440     free (sys);
441     (void) crd;
442 }
443
444
445 /**
446  * Initializes a server-side TLS session.
447  */
448 static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
449                                int type, int fd)
450 {
451     vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
452     if (unlikely(sys == NULL))
453         return VLC_ENOMEM;
454
455     session->sys = sys;
456     session->sock.p_sys = session;
457     session->sock.pf_send = gnutls_Send;
458     session->sock.pf_recv = gnutls_Recv;
459     session->handshake = crd->sys->handshake;
460     sys->handshaked = false;
461
462     int val = gnutls_init (&sys->session, type);
463     if (val != 0)
464     {
465         msg_Err (session, "cannot initialize TLS session: %s",
466                  gnutls_strerror (val));
467         free (sys);
468         return VLC_EGENERIC;
469     }
470
471     if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
472         goto error;
473
474     val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
475                                   crd->sys->x509_cred);
476     if (val < 0)
477     {
478         msg_Err (session, "cannot set TLS session credentials: %s",
479                  gnutls_strerror (val));
480         goto error;
481     }
482
483     gnutls_transport_set_ptr (sys->session,
484                               (gnutls_transport_ptr_t)(intptr_t)fd);
485     return VLC_SUCCESS;
486
487 error:
488     gnutls_SessionClose (crd, session);
489     return VLC_EGENERIC;
490 }
491
492 static int gnutls_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
493                                      int fd, const char *hostname)
494 {
495     int val = gnutls_SessionOpen (crd, session, GNUTLS_SERVER, fd);
496     if (val != VLC_SUCCESS)
497         return val;
498
499     if (session->handshake == gnutls_HandshakeAndValidate)
500         gnutls_certificate_server_set_request (session->sys->session,
501                                                GNUTLS_CERT_REQUIRE);
502     assert (hostname == NULL);
503     return VLC_SUCCESS;
504 }
505
506 static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
507                                      int fd, const char *hostname)
508 {
509     int val = gnutls_SessionOpen (crd, session, GNUTLS_CLIENT, fd);
510     if (val != VLC_SUCCESS)
511         return val;
512
513     vlc_tls_sys_t *sys = session->sys;
514
515     /* minimum DH prime bits */
516     gnutls_dh_set_prime_bits (sys->session, 1024);
517
518     if (likely(hostname != NULL))
519         /* fill Server Name Indication */
520         gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
521                                 hostname, strlen (hostname));
522
523     return VLC_SUCCESS;
524 }
525
526
527 /**
528  * Adds one or more Certificate Authorities to the trusted set.
529  *
530  * @param path (UTF-8) path to an X.509 certificates list.
531  *
532  * @return -1 on error, 0 on success.
533  */
534 static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
535 {
536     block_t *block = block_FilePath (path);
537     if (block == NULL)
538     {
539         msg_Err (crd, "cannot read trusted CA from %s: %m", path);
540         return VLC_EGENERIC;
541     }
542
543     gnutls_datum_t d = {
544        .data = block->p_buffer,
545        .size = block->i_buffer,
546     };
547
548     int val = gnutls_certificate_set_x509_trust_mem (crd->sys->x509_cred, &d,
549                                                      GNUTLS_X509_FMT_PEM);
550     block_Release (block);
551     if (val < 0)
552     {
553         msg_Err (crd, "cannot load trusted CA from %s: %s", path,
554                  gnutls_strerror (val));
555         return VLC_EGENERIC;
556     }
557     msg_Dbg (crd, " %d trusted CA%s added from %s", val, (val != 1) ? "s" : "",
558              path);
559
560     /* enables peer's certificate verification */
561     crd->sys->handshake = gnutls_HandshakeAndValidate;
562     return VLC_SUCCESS;
563 }
564
565
566 /**
567  * Adds a Certificates Revocation List to be sent to TLS clients.
568  *
569  * @param path (UTF-8) path of the CRL file.
570  *
571  * @return -1 on error, 0 on success.
572  */
573 static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
574 {
575     block_t *block = block_FilePath (path);
576     if (block == NULL)
577     {
578         msg_Err (crd, "cannot read CRL from %s: %m", path);
579         return VLC_EGENERIC;
580     }
581
582     gnutls_datum_t d = {
583        .data = block->p_buffer,
584        .size = block->i_buffer,
585     };
586
587     int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
588                                                    GNUTLS_X509_FMT_PEM);
589     block_Release (block);
590     if (val < 0)
591     {
592         msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
593         return VLC_EGENERIC;
594     }
595     msg_Dbg (crd, "%d CRL%s added from %s", val, (val != 1) ? "s" : "", path);
596     return VLC_SUCCESS;
597 }
598
599
600 /**
601  * Allocates a whole server's TLS credentials.
602  */
603 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
604 {
605     int val;
606
607     if (gnutls_Init (VLC_OBJECT(crd)))
608         return VLC_EGENERIC;
609
610     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
611     if (unlikely(sys == NULL))
612         goto error;
613
614     crd->sys     = sys;
615     crd->add_CA  = gnutls_AddCA;
616     crd->add_CRL = gnutls_AddCRL;
617     crd->open    = gnutls_ServerSessionOpen;
618     crd->close   = gnutls_SessionClose;
619     /* No certificate validation by default */
620     sys->handshake  = gnutls_ContinueHandshake;
621
622     /* Sets server's credentials */
623     val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
624     if (val != 0)
625     {
626         msg_Err (crd, "cannot allocate credentials: %s",
627                  gnutls_strerror (val));
628         goto error;
629     }
630
631     block_t *certblock = block_FilePath (cert);
632     if (certblock == NULL)
633     {
634         msg_Err (crd, "cannot read certificate chain from %s: %m", cert);
635         return VLC_EGENERIC;
636     }
637
638     block_t *keyblock = block_FilePath (key);
639     if (keyblock == NULL)
640     {
641         msg_Err (crd, "cannot read private key from %s: %m", key);
642         block_Release (certblock);
643         return VLC_EGENERIC;
644     }
645
646     gnutls_datum_t pub = {
647        .data = certblock->p_buffer,
648        .size = certblock->i_buffer,
649     }, priv = {
650        .data = keyblock->p_buffer,
651        .size = keyblock->i_buffer,
652     };
653
654     val = gnutls_certificate_set_x509_key_mem (sys->x509_cred, &pub, &priv,
655                                                 GNUTLS_X509_FMT_PEM);
656     block_Release (keyblock);
657     block_Release (certblock);
658     if (val < 0)
659     {
660         msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
661         gnutls_certificate_free_credentials (sys->x509_cred);
662         goto error;
663     }
664
665     /* FIXME:
666      * - support other cipher suites
667      */
668     val = gnutls_dh_params_init (&sys->dh_params);
669     if (val >= 0)
670     {
671         const gnutls_datum_t data = {
672             .data = (unsigned char *)dh_params,
673             .size = sizeof (dh_params) - 1,
674         };
675
676         val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
677                                              GNUTLS_X509_FMT_PEM);
678         if (val == 0)
679             gnutls_certificate_set_dh_params (sys->x509_cred,
680                                               sys->dh_params);
681     }
682     if (val < 0)
683     {
684         msg_Err (crd, "cannot initialize DHE cipher suites: %s",
685                  gnutls_strerror (val));
686     }
687
688     return VLC_SUCCESS;
689
690 error:
691     free (sys);
692     gnutls_Deinit (VLC_OBJECT(crd));
693     return VLC_EGENERIC;
694 }
695
696 /**
697  * Destroys a TLS server object.
698  */
699 static void CloseServer (vlc_tls_creds_t *crd)
700 {
701     vlc_tls_creds_sys_t *sys = crd->sys;
702
703     /* all sessions depending on the server are now deinitialized */
704     gnutls_certificate_free_credentials (sys->x509_cred);
705     gnutls_dh_params_deinit (sys->dh_params);
706     free (sys);
707
708     gnutls_Deinit (VLC_OBJECT(crd));
709 }
710
711 /**
712  * Initializes a client-side TLS credentials.
713  */
714 static int OpenClient (vlc_tls_creds_t *crd)
715 {
716     if (gnutls_Init (VLC_OBJECT(crd)))
717         return VLC_EGENERIC;
718
719     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
720     if (unlikely(sys == NULL))
721         goto error;
722
723     crd->sys = sys;
724     //crd->add_CA = gnutls_AddCA;
725     //crd->add_CRL = gnutls_AddCRL;
726     crd->open = gnutls_ClientSessionOpen;
727     crd->close = gnutls_SessionClose;
728     sys->handshake = gnutls_HandshakeAndValidate;
729
730     int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
731     if (val != 0)
732     {
733         msg_Err (crd, "cannot allocate credentials: %s",
734                  gnutls_strerror (val));
735         goto error;
736     }
737
738     val = gnutls_certificate_set_x509_system_trust (sys->x509_cred);
739     if (val < 0)
740         msg_Err (crd, "cannot load trusted Certificate Authorities: %s",
741                  gnutls_strerror (val));
742     else
743         msg_Dbg (crd, "loaded %d trusted CAs", val);
744
745     gnutls_certificate_set_verify_flags (sys->x509_cred,
746                                          GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
747
748     return VLC_SUCCESS;
749 error:
750     free (sys);
751     gnutls_Deinit (VLC_OBJECT(crd));
752     return VLC_EGENERIC;
753 }
754
755 static void CloseClient (vlc_tls_creds_t *crd)
756 {
757     vlc_tls_creds_sys_t *sys = crd->sys;
758
759     gnutls_certificate_free_credentials (sys->x509_cred);
760     free (sys);
761
762     gnutls_Deinit (VLC_OBJECT(crd));
763 }