gnutls.c 22.7 KB
Newer Older
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1
/*****************************************************************************
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
2
 * gnutls.c
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
3
 *****************************************************************************
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
4
 * Copyright (C) 2004-2012 Rémi Denis-Courmont
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
5
6
 *
 * This program is free software; you can redistribute it and/or modify
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
7
8
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
9
10
11
12
13
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
14
 * GNU Lesser General Public License for more details.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
15
 *
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
16
17
18
 * You should have received a copy of the GNU Öesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
19
20
21
22
23
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Cleanup    
Rémi Denis-Courmont committed
24

25
26
27
28
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

29
#include <time.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
30
#include <errno.h>
31
#include <assert.h>
32

33
34
#include <vlc_common.h>
#include <vlc_plugin.h>
35
#include <vlc_tls.h>
36
#include <vlc_block.h>
37
#include <vlc_dialog.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
38
39

#include <gnutls/gnutls.h>
40
#include <gnutls/x509.h>
41
42
#include "dhparams.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43
44
45
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
46
47
static int  OpenClient  (vlc_tls_creds_t *);
static void CloseClient (vlc_tls_creds_t *);
48
49
static int  OpenServer  (vlc_tls_creds_t *, const char *, const char *);
static void CloseServer (vlc_tls_creds_t *);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
50

51
52
53
54
#define PRIORITIES_TEXT N_("TLS cipher priorities")
#define PRIORITIES_LONGTEXT N_("Ciphers, key exchange methods, " \
    "hash functions and compression methods can be selected. " \
    "Refer to GNU TLS documentation for detailed syntax.")
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static const char *const priorities_values[] = {
    "PERFORMANCE",
    "NORMAL",
    "SECURE128",
    "SECURE256",
    "EXPORT",
};
static const char *const priorities_text[] = {
    N_("Performance (prioritize faster ciphers)"),
    N_("Normal"),
    N_("Secure 128-bits (exclude 256-bits ciphers)"),
    N_("Secure 256-bits (prioritize 256-bits ciphers)"),
    N_("Export (include insecure ciphers)"),
};
69

70
vlc_module_begin ()
71
72
    set_shortname( "GNU TLS" )
    set_description( N_("GNU TLS transport layer security") )
73
74
75
    set_capability( "tls client", 1 )
    set_callbacks( OpenClient, CloseClient )
    set_category( CAT_ADVANCED )
76
    set_subcategory( SUBCAT_ADVANCED_NETWORK )
77
78

    add_submodule ()
79
        set_description( N_("GNU TLS server") )
80
81
        set_capability( "tls server", 1 )
        set_category( CAT_ADVANCED )
82
        set_subcategory( SUBCAT_ADVANCED_NETWORK )
83
84
        set_callbacks( OpenServer, CloseServer )

85
86
        add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
                    PRIORITIES_LONGTEXT, false)
87
            change_string_list (priorities_values, priorities_text)
88
vlc_module_end ()
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
89

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
90
91
static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;

92
93
94
95
96
/**
 * Initializes GnuTLS with proper locking.
 * @return VLC_SUCCESS on success, a VLC error code otherwise.
 */
static int gnutls_Init (vlc_object_t *p_this)
97
{
98
99
    int ret = VLC_EGENERIC;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
100
    vlc_mutex_lock (&gnutls_mutex);
101
102
103
104
105
106
    if (gnutls_global_init ())
    {
        msg_Err (p_this, "cannot initialize GnuTLS");
        goto error;
    }

107
    const char *psz_version = gnutls_check_version ("3.0.20");
108
109
110
111
112
113
114
115
116
117
118
    if (psz_version == NULL)
    {
        msg_Err (p_this, "unsupported GnuTLS version");
        gnutls_global_deinit ();
        goto error;
    }

    msg_Dbg (p_this, "GnuTLS v%s initialized", psz_version);
    ret = VLC_SUCCESS;

error:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
119
    vlc_mutex_unlock (&gnutls_mutex);
120
121
122
123
124
125
126
127
128
    return ret;
}


/**
 * Deinitializes GnuTLS.
 */
static void gnutls_Deinit (vlc_object_t *p_this)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129
    vlc_mutex_lock (&gnutls_mutex);
130
131
132

    gnutls_global_deinit ();
    msg_Dbg (p_this, "GnuTLS deinitialized");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
133
    vlc_mutex_unlock (&gnutls_mutex);
134
}
135
136


137
138
139
140
141
static int gnutls_Error (vlc_object_t *obj, int val)
{
    switch (val)
    {
        case GNUTLS_E_AGAIN:
142
#ifdef _WIN32
143
144
            WSASetLastError (WSAEWOULDBLOCK);
#else
145
            errno = EAGAIN;
146
#endif
147
            break;
148
149

        case GNUTLS_E_INTERRUPTED:
150
#ifdef _WIN32
151
            WSASetLastError (WSAEINTR);
152
#else
153
            errno = EINTR;
154
#endif
155
156
157
158
            break;

        default:
            msg_Err (obj, "%s", gnutls_strerror (val));
159
#ifndef NDEBUG
160
161
162
            if (!gnutls_error_is_fatal (val))
                msg_Err (obj, "Error above should be handled");
#endif
163
#ifdef _WIN32
164
            WSASetLastError (WSAECONNRESET);
165
#else
166
            errno = ECONNRESET;
167
#endif
168
169
170
    }
    return -1;
}
171
#define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
172

173
struct vlc_tls_sys
174
{
175
    gnutls_session_t session;
176
    bool handshaked;
177
178
179
};


180
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
181
 * Sends data through a TLS session.
182
 */
183
static int gnutls_Send (void *opaque, const void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
184
{
185
186
    vlc_tls_t *session = opaque;
    vlc_tls_sys_t *sys = session->sys;
187

188
189
    int val = gnutls_record_send (sys->session, buf, length);
    return (val < 0) ? gnutls_Error (session, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
190
191
192
}


193
/**
194
 * Receives data through a TLS session.
195
 */
196
static int gnutls_Recv (void *opaque, void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
197
{
198
199
    vlc_tls_t *session = opaque;
    vlc_tls_sys_t *sys = session->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
200

201
202
    int val = gnutls_record_recv (sys->session, buf, length);
    return (val < 0) ? gnutls_Error (session, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
203
204
205
}


206
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
207
208
 * Starts or continues the TLS handshake.
 *
Rémi Denis-Courmont's avatar
Typos    
Rémi Denis-Courmont committed
209
 * @return -1 on fatal error, 0 on successful handshake completion,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
210
211
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
212
 */
213
214
static int gnutls_ContinueHandshake (vlc_tls_t *session, const char *host,
                                     const char *service)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
215
{
216
    vlc_tls_sys_t *sys = session->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
217
218
    int val;

219
#ifdef _WIN32
220
    WSASetLastError (0);
221
#endif
222
223
224
225
226
227
228
229
230
231
    do
    {
        val = gnutls_handshake (sys->session);
        msg_Dbg (session, "TLS handshake: %s", gnutls_strerror (val));

        if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
            /* I/O event: return to caller's poll() loop */
            return 1 + gnutls_record_get_direction (sys->session);
    }
    while (val < 0 && !gnutls_error_is_fatal (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
232

233
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
234
    {
235
#ifdef _WIN32
236
        msg_Dbg (session, "Winsock error %d", WSAGetLastError ());
237
#endif
238
        msg_Err (session, "TLS handshake error: %s", gnutls_strerror (val));
239
        return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
240
    }
241

242
    sys->handshaked = true;
243
    (void) host; (void) service;
244
245
246
    return 0;
}

247

248
249
250
251
252
/**
 * Looks up certificate in known hosts data base.
 * @return 0 on success, -1 on failure.
 */
static int gnutls_CertSearch (vlc_tls_t *obj, const char *host,
253
                              const char *service,
254
255
256
                              const gnutls_datum_t *restrict datum)
{
    assert (host != NULL);
257

258
259
260
    /* Look up mismatching certificate in store */
    int val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
                                           GNUTLS_CRT_X509, datum, 0);
261
    const char *msg;
262
263
264
265
266
267
268
    switch (val)
    {
        case 0:
            msg_Dbg (obj, "certificate key match for %s", host);
            return 0;
        case GNUTLS_E_NO_CERTIFICATE_FOUND:
            msg_Dbg (obj, "no known certificates for %s", host);
269
270
271
            msg = N_("You attempted to reach %s. "
                "However the security certificate presented by the server "
                "is unknown and could not be authenticated by any trusted "
Pierre Ynard's avatar
Pierre Ynard committed
272
                "Certification Authority. "
273
274
275
                "This problem may be caused by a configuration error "
                "or an attempt to breach your security or your privacy.\n\n"
                "If in doubt, abort now.\n");
276
277
278
            break;
        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
            msg_Dbg (obj, "certificate keys mismatch for %s", host);
279
280
281
            msg = N_("You attempted to reach %s. "
                "However the security certificate presented by the server "
                "changed since the previous visit "
Pierre Ynard's avatar
Pierre Ynard committed
282
283
                "and was not authenticated by any trusted "
                "Certification Authority. "
284
285
286
                "This problem may be caused by a configuration error "
                "or an attempt to breach your security or your privacy.\n\n"
                "If in doubt, abort now.\n");
287
288
289
290
291
292
            break;
        default:
            msg_Err (obj, "certificate key match error for %s: %s", host,
                     gnutls_strerror (val));
            return -1;
    }
293

294
    if (dialog_Question (obj, _("Insecure site"), vlc_gettext (msg),
295
                         _("Abort"), _("View certificate"), NULL, host) != 2)
296
        return -1;
297
298
299
300
301
302
303
304
305
306
307
308
309
310

    gnutls_x509_crt_t cert;
    gnutls_datum_t desc;

    if (gnutls_x509_crt_init (&cert))
        return -1;
    if (gnutls_x509_crt_import (cert, datum, GNUTLS_X509_FMT_DER)
     || gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &desc))
    {
        gnutls_x509_crt_deinit (cert);
        return -1;
    }
    gnutls_x509_crt_deinit (cert);

311
312
313
314
315
    val = dialog_Question (obj, _("Insecure site"),
         _("This is the certificate presented by %s:\n%s\n\n"
           "If in doubt, abort now.\n"),
                           _("Abort"), _("Accept 24 hours"),
                           _("Accept permanently"), host, desc.data);
316
317
    gnutls_free (desc.data);

318
319
320
321
322
323
324
    time_t expiry = 0;
    switch (val)
    {
        case 2:
            time (&expiry);
            expiry += 24 * 60 * 60;
        case 3:
325
326
327
328
329
            val = gnutls_store_pubkey (NULL, NULL, host, service,
                                       GNUTLS_CRT_X509, datum, expiry, 0);
            if (val)
                msg_Err (obj, "cannot store X.509 certificate: %s",
                         gnutls_strerror (val));
330
331
332
            return 0;
    }
    return -1;
333
334
335
}


336
static struct
337
{
338
339
    unsigned flag;
    const char msg[29];
340
} cert_errs[] =
341
{
342
343
344
345
346
347
348
    { GNUTLS_CERT_INVALID,            "Certificate not verified"     },
    { GNUTLS_CERT_REVOKED,            "Certificate revoked"          },
    { GNUTLS_CERT_SIGNER_NOT_FOUND,   "Signer not found"             },
    { GNUTLS_CERT_SIGNER_NOT_CA,      "Signer not a CA"              },
    { GNUTLS_CERT_INSECURE_ALGORITHM, "Signature algorithm insecure" },
    { GNUTLS_CERT_NOT_ACTIVATED,      "Certificate not activated"    },
    { GNUTLS_CERT_EXPIRED,            "Certificate expired"          },
349
350
351
};


352
353
static int gnutls_HandshakeAndValidate (vlc_tls_t *session, const char *host,
                                        const char *service)
354
{
355
    vlc_tls_sys_t *sys = session->sys;
356

357
    int val = gnutls_ContinueHandshake (session, host, service);
358
359
    if (val)
        return val;
360

361
362
363
    /* certificates chain verification */
    unsigned status;

364
365
    val = gnutls_certificate_verify_peers2 (sys->session, &status);
    if (val)
366
    {
367
        msg_Err (session, "Certificate verification error: %s",
368
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
369
        return -1;
370
    }
371
    if (status)
372
    {
373
        msg_Err (session, "Certificate verification failure (0x%04X)", status);
374
375
376
        for (size_t i = 0; i < sizeof (cert_errs) / sizeof (cert_errs[0]); i++)
            if (status & cert_errs[i].flag)
                msg_Err (session, " * %s", cert_errs[i].msg);
377
378
        if (status & ~(GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND))
            return -1;
379
380
    }

381
382
383
    if (host == NULL)
        return status ? -1 : 0;

384
    /* certificate (host)name verification */
385
    const gnutls_datum_t *data;
386
387
388
    unsigned count;
    data = gnutls_certificate_get_peers (sys->session, &count);
    if (data == NULL || count == 0)
389
    {
390
        msg_Err (session, "Peer certificate not available");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
391
        return -1;
392
    }
393
394
    msg_Dbg (session, "%u certificate(s) in the list", count);

395
    if (status && gnutls_CertSearch (session, host, service, data))
396
        return -1;
397

398
    gnutls_x509_crt_t cert;
399
400
    val = gnutls_x509_crt_init (&cert);
    if (val)
401
    {
402
        msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
403
        return -1;
404
405
    }

406
407
    val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
    if (val)
408
    {
409
410
        msg_Err (session, "Certificate import error: %s",
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
411
        goto error;
412
    }
413

414
415
    val = !gnutls_x509_crt_check_hostname (cert, host);
    if (val)
416
    {
417
        msg_Err (session, "Certificate does not match \"%s\"", host);
418
        val = gnutls_CertSearch (session, host, service, data);
419
420
    }
error:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
421
    gnutls_x509_crt_deinit (cert);
422
    return val;
423
424
}

425
426
427
static int
gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
{
428
429
430
    char *priorities = var_InheritString (obj, "gnutls-priorities");
    if (unlikely(priorities == NULL))
        return VLC_ENOMEM;
431

432
433
    const char *errp;
    int val = gnutls_priority_set_direct (session, priorities, &errp);
434
435
    if (val < 0)
    {
436
        msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
437
                 gnutls_strerror (val));
438
        val = VLC_EGENERIC;
439
    }
440
441
442
443
    else
        val = VLC_SUCCESS;
    free (priorities);
    return val;
444
445
}

446

447
/**
448
 * TLS credentials private data
449
 */
450
struct vlc_tls_creds_sys
451
{
452
    gnutls_certificate_credentials_t x509_cred;
453
    gnutls_dh_params_t dh_params; /* XXX: used for server only */
454
455
    int (*handshake) (vlc_tls_t *, const char *, const char *);
        /* ^^ XXX: useful for server only */
456
457
458
459
460
461
462
};


/**
 * Terminates TLS session and releases session data.
 * You still have to close the socket yourself.
 */
463
static void gnutls_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session)
464
{
465
    vlc_tls_sys_t *sys = session->sys;
466

467
468
469
    if (sys->handshaked)
        gnutls_bye (sys->session, GNUTLS_SHUT_WR);
    gnutls_deinit (sys->session);
470

471
    free (sys);
472
    (void) crd;
473
474
475
}


476
477
478
/**
 * Initializes a server-side TLS session.
 */
479
static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
480
                               int type, int fd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
481
{
482
483
    vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
    if (unlikely(sys == NULL))
484
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
485

486
487
488
489
    session->sys = sys;
    session->sock.p_sys = session;
    session->sock.pf_send = gnutls_Send;
    session->sock.pf_recv = gnutls_Recv;
490
    session->handshake = crd->sys->handshake;
491
    sys->handshaked = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
492

493
    int val = gnutls_init (&sys->session, type);
494
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
495
    {
496
        msg_Err (session, "cannot initialize TLS session: %s",
497
498
                 gnutls_strerror (val));
        free (sys);
499
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
500
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
501

502
    if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
503
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
504

505
    val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
506
                                  crd->sys->x509_cred);
507
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
508
    {
509
        msg_Err (session, "cannot set TLS session credentials: %s",
510
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
511
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
512
513
    }

514
515
    gnutls_transport_set_ptr (sys->session,
                              (gnutls_transport_ptr_t)(intptr_t)fd);
516
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
517
518

error:
519
520
    gnutls_SessionClose (crd, session);
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
521
522
}

523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
static int gnutls_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
                                     int fd, const char *hostname)
{
    int val = gnutls_SessionOpen (crd, session, GNUTLS_SERVER, fd);
    if (val != VLC_SUCCESS)
        return val;

    if (session->handshake == gnutls_HandshakeAndValidate)
        gnutls_certificate_server_set_request (session->sys->session,
                                               GNUTLS_CERT_REQUIRE);
    assert (hostname == NULL);
    return VLC_SUCCESS;
}

static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
                                     int fd, const char *hostname)
{
    int val = gnutls_SessionOpen (crd, session, GNUTLS_CLIENT, fd);
    if (val != VLC_SUCCESS)
        return val;

    vlc_tls_sys_t *sys = session->sys;

    /* minimum DH prime bits */
    gnutls_dh_set_prime_bits (sys->session, 1024);

    if (likely(hostname != NULL))
        /* fill Server Name Indication */
        gnutls_server_name_set (sys->session, GNUTLS_NAME_DNS,
                                hostname, strlen (hostname));

    return VLC_SUCCESS;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
557

558
/**
559
 * Adds one or more Certificate Authorities to the trusted set.
560
 *
561
 * @param path (UTF-8) path to an X.509 certificates list.
562
563
 *
 * @return -1 on error, 0 on success.
564
 */
565
static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
566
{
567
568
569
    block_t *block = block_FilePath (path);
    if (block == NULL)
    {
570
571
        msg_Err (crd, "cannot read trusted CA from %s: %s", path,
                 vlc_strerror_c(errno));
572
573
574
575
576
577
578
        return VLC_EGENERIC;
    }

    gnutls_datum_t d = {
       .data = block->p_buffer,
       .size = block->i_buffer,
    };
579

580
581
582
    int val = gnutls_certificate_set_x509_trust_mem (crd->sys->x509_cred, &d,
                                                     GNUTLS_X509_FMT_PEM);
    block_Release (block);
583
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
584
    {
585
        msg_Err (crd, "cannot load trusted CA from %s: %s", path,
586
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
587
588
        return VLC_EGENERIC;
    }
589
590
    msg_Dbg (crd, " %d trusted CA%s added from %s", val, (val != 1) ? "s" : "",
             path);
591
592

    /* enables peer's certificate verification */
593
    crd->sys->handshake = gnutls_HandshakeAndValidate;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
594
595
596
597
    return VLC_SUCCESS;
}


598
/**
599
 * Adds a Certificates Revocation List to be sent to TLS clients.
600
 *
601
 * @param path (UTF-8) path of the CRL file.
602
603
604
 *
 * @return -1 on error, 0 on success.
 */
605
static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
606
{
607
608
609
    block_t *block = block_FilePath (path);
    if (block == NULL)
    {
610
611
        msg_Err (crd, "cannot read CRL from %s: %s", path,
                 vlc_strerror_c(errno));
612
613
614
615
616
617
618
        return VLC_EGENERIC;
    }

    gnutls_datum_t d = {
       .data = block->p_buffer,
       .size = block->i_buffer,
    };
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
619

620
621
622
    int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
                                                   GNUTLS_X509_FMT_PEM);
    block_Release (block);
623
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
624
    {
625
        msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
626
627
        return VLC_EGENERIC;
    }
628
    msg_Dbg (crd, "%d CRL%s added from %s", val, (val != 1) ? "s" : "", path);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
629
630
    return VLC_SUCCESS;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
631

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
632

633
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
634
 * Allocates a whole server's TLS credentials.
635
 */
636
static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
637
638
639
{
    int val;

640
    if (gnutls_Init (VLC_OBJECT(crd)))
641
642
        return VLC_EGENERIC;

643
644
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
645
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
646

647
    crd->sys     = sys;
648
649
    crd->add_CA  = gnutls_AddCA;
    crd->add_CRL = gnutls_AddCRL;
650
    crd->open    = gnutls_ServerSessionOpen;
651
    crd->close   = gnutls_SessionClose;
652
    /* No certificate validation by default */
653
    sys->handshake  = gnutls_ContinueHandshake;
654

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
655
    /* Sets server's credentials */
656
657
    val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
658
    {
659
        msg_Err (crd, "cannot allocate credentials: %s",
660
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
661
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
662
663
    }

664
665
666
    block_t *certblock = block_FilePath (cert);
    if (certblock == NULL)
    {
667
668
        msg_Err (crd, "cannot read certificate chain from %s: %s", cert,
                 vlc_strerror_c(errno));
669
670
671
672
673
674
        return VLC_EGENERIC;
    }

    block_t *keyblock = block_FilePath (key);
    if (keyblock == NULL)
    {
675
676
        msg_Err (crd, "cannot read private key from %s: %s", key,
                 vlc_strerror_c(errno));
677
678
679
680
681
682
683
684
685
686
687
688
689
        block_Release (certblock);
        return VLC_EGENERIC;
    }

    gnutls_datum_t pub = {
       .data = certblock->p_buffer,
       .size = certblock->i_buffer,
    }, priv = {
       .data = keyblock->p_buffer,
       .size = keyblock->i_buffer,
    };

    val = gnutls_certificate_set_x509_key_mem (sys->x509_cred, &pub, &priv,
690
                                                GNUTLS_X509_FMT_PEM);
691
692
    block_Release (keyblock);
    block_Release (certblock);
693
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
694
    {
695
        msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
696
        gnutls_certificate_free_credentials (sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
697
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
698
699
    }

700
    /* FIXME:
701
     * - support other cipher suites
702
     */
703
    val = gnutls_dh_params_init (&sys->dh_params);
704
705
    if (val >= 0)
    {
706
707
708
709
710
        const gnutls_datum_t data = {
            .data = (unsigned char *)dh_params,
            .size = sizeof (dh_params) - 1,
        };

711
        val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
712
713
                                             GNUTLS_X509_FMT_PEM);
        if (val == 0)
714
715
            gnutls_certificate_set_dh_params (sys->x509_cred,
                                              sys->dh_params);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
716
    }
717
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
718
    {
719
        msg_Err (crd, "cannot initialize DHE cipher suites: %s",
720
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
721
722
    }

723
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
724
725

error:
726
    free (sys);
727
    gnutls_Deinit (VLC_OBJECT(crd));
728
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
729
730
}

731
/**
732
 * Destroys a TLS server object.
733
 */
734
static void CloseServer (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
735
{
736
    vlc_tls_creds_sys_t *sys = crd->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
737

738
    /* all sessions depending on the server are now deinitialized */
739
740
741
    gnutls_certificate_free_credentials (sys->x509_cred);
    gnutls_dh_params_deinit (sys->dh_params);
    free (sys);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
742

743
    gnutls_Deinit (VLC_OBJECT(crd));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
744
}
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798

/**
 * Initializes a client-side TLS credentials.
 */
static int OpenClient (vlc_tls_creds_t *crd)
{
    if (gnutls_Init (VLC_OBJECT(crd)))
        return VLC_EGENERIC;

    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
        goto error;

    crd->sys = sys;
    //crd->add_CA = gnutls_AddCA;
    //crd->add_CRL = gnutls_AddCRL;
    crd->open = gnutls_ClientSessionOpen;
    crd->close = gnutls_SessionClose;
    sys->handshake = gnutls_HandshakeAndValidate;

    int val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
    if (val != 0)
    {
        msg_Err (crd, "cannot allocate credentials: %s",
                 gnutls_strerror (val));
        goto error;
    }

    val = gnutls_certificate_set_x509_system_trust (sys->x509_cred);
    if (val < 0)
        msg_Err (crd, "cannot load trusted Certificate Authorities: %s",
                 gnutls_strerror (val));
    else
        msg_Dbg (crd, "loaded %d trusted CAs", val);

    gnutls_certificate_set_verify_flags (sys->x509_cred,
                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);

    return VLC_SUCCESS;
error:
    free (sys);
    gnutls_Deinit (VLC_OBJECT(crd));
    return VLC_EGENERIC;
}

static void CloseClient (vlc_tls_creds_t *crd)
{
    vlc_tls_creds_sys_t *sys = crd->sys;

    gnutls_certificate_free_credentials (sys->x509_cred);
    free (sys);

    gnutls_Deinit (VLC_OBJECT(crd));
}