gnutls.c 22.3 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
43
44
#if (GNUTLS_VERSION_NUMBER < 0x030014)
# define gnutls_certificate_set_x509_system_trust(c) \
    (c, GNUTLS_E_UNIMPLEMENTED_FEATURE)
#endif
45
46
47
48
49
50
#if (GNUTLS_VERSION_NUMBER < 0x03000D)
# define gnutls_verify_stored_pubkey(db,tdb,host,serv,ctype,cert,fl) \
    (db, host, serv, ctype, cert, fl, GNUTLS_E_NO_CERTIFICATE_FOUND)
# define gnutls_store_pubkey(db,tdb,host,serv,ctype,cert,e,fl) \
    (db, host, serv, ctype, cert, fl, GNUTLS_E_UNIMPLEMENTED_FEATURE)
#endif
51
52
#include "dhparams.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
53
54
55
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
56
57
static int  OpenClient  (vlc_tls_creds_t *);
static void CloseClient (vlc_tls_creds_t *);
58
59
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
60

61
62
63
64
#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.")
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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)"),
};
79

80
vlc_module_begin ()
81
82
    set_shortname( "GNU TLS" )
    set_description( N_("GNU TLS transport layer security") )
83
84
85
86
87
88
    set_capability( "tls client", 1 )
    set_callbacks( OpenClient, CloseClient )
    set_category( CAT_ADVANCED )
    set_subcategory( SUBCAT_ADVANCED_MISC )

    add_submodule ()
89
        set_description( N_("GNU TLS server") )
90
91
92
93
94
        set_capability( "tls server", 1 )
        set_category( CAT_ADVANCED )
        set_subcategory( SUBCAT_ADVANCED_MISC )
        set_callbacks( OpenServer, CloseServer )

95
96
        add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
                    PRIORITIES_LONGTEXT, false)
97
            change_string_list (priorities_values, priorities_text)
98
vlc_module_end ()
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
99

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

102
103
104
105
106
/**
 * Initializes GnuTLS with proper locking.
 * @return VLC_SUCCESS on success, a VLC error code otherwise.
 */
static int gnutls_Init (vlc_object_t *p_this)
107
{
108
109
    int ret = VLC_EGENERIC;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
110
    vlc_mutex_lock (&gnutls_mutex);
111
112
113
114
115
116
    if (gnutls_global_init ())
    {
        msg_Err (p_this, "cannot initialize GnuTLS");
        goto error;
    }

117
    const char *psz_version = gnutls_check_version ("2.6.6");
118
119
120
121
122
123
124
125
126
127
128
    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
129
    vlc_mutex_unlock (&gnutls_mutex);
130
131
132
133
134
135
136
137
138
    return ret;
}


/**
 * Deinitializes GnuTLS.
 */
static void gnutls_Deinit (vlc_object_t *p_this)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
139
    vlc_mutex_lock (&gnutls_mutex);
140
141
142

    gnutls_global_deinit ();
    msg_Dbg (p_this, "GnuTLS deinitialized");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
143
    vlc_mutex_unlock (&gnutls_mutex);
144
}
145
146


147
148
149
150
151
static int gnutls_Error (vlc_object_t *obj, int val)
{
    switch (val)
    {
        case GNUTLS_E_AGAIN:
152
153
154
#ifdef WIN32
            WSASetLastError (WSAEWOULDBLOCK);
#else
155
            errno = EAGAIN;
156
#endif
157
            break;
158
159

        case GNUTLS_E_INTERRUPTED:
160
161
#ifdef WIN32
            WSASetLastError (WSAEINTR);
162
#else
163
            errno = EINTR;
164
#endif
165
166
167
168
            break;

        default:
            msg_Err (obj, "%s", gnutls_strerror (val));
169
#ifndef NDEBUG
170
171
172
            if (!gnutls_error_is_fatal (val))
                msg_Err (obj, "Error above should be handled");
#endif
173
174
#ifdef WIN32
            WSASetLastError (WSAECONNRESET);
175
#else
176
            errno = ECONNRESET;
177
#endif
178
179
180
    }
    return -1;
}
181
#define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
182

183
struct vlc_tls_sys
184
{
185
    gnutls_session_t session;
186
    bool handshaked;
187
188
189
};


190
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
191
 * Sends data through a TLS session.
192
 */
193
static int gnutls_Send (void *opaque, const void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
194
{
195
196
    vlc_tls_t *session = opaque;
    vlc_tls_sys_t *sys = session->sys;
197

198
199
    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
200
201
202
}


203
/**
204
 * Receives data through a TLS session.
205
 */
206
static int gnutls_Recv (void *opaque, void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
207
{
208
209
    vlc_tls_t *session = opaque;
    vlc_tls_sys_t *sys = session->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
210

211
212
    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
213
214
215
}


216
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
217
218
 * Starts or continues the TLS handshake.
 *
Rémi Denis-Courmont's avatar
Typos    
Rémi Denis-Courmont committed
219
 * @return -1 on fatal error, 0 on successful handshake completion,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
220
221
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
222
 */
223
224
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
225
{
226
    vlc_tls_sys_t *sys = session->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
227
228
    int val;

229
#ifdef WIN32
230
    WSASetLastError (0);
231
#endif
232
233
234
    val = gnutls_handshake (sys->session);
    if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
        return 1 + gnutls_record_get_direction (sys->session);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
235

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

245
    sys->handshaked = true;
246
    (void) host; (void) service;
247
248
249
    return 0;
}

250

251
252
253
254
255
/**
 * 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,
256
                              const char *service,
257
258
259
                              const gnutls_datum_t *restrict datum)
{
    assert (host != NULL);
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    /* Look up mismatching certificate in store */
    int val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
                                           GNUTLS_CRT_X509, datum, 0);
    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);
            break;
        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
            msg_Dbg (obj, "certificate keys mismatch for %s", host);
            break;
        default:
            msg_Err (obj, "certificate key match error for %s: %s", host,
                     gnutls_strerror (val));
            return -1;
    }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301

    if (dialog_Question (obj, N_("Insecure site"),
         N_("You attempted to reach %s, but security certificate presented by "
            "the server could not be verified."
            "This problem may be caused by a configuration error "
            "on the server or by a serious breach of network security.\n\n"
            "If in doubt, abort now.\n"),
                         N_("Abort"), N_("View certificate"), NULL, host) != 2)
         return -1;

    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);

302
    val = dialog_Question (obj, N_("Insecure site"),
303
304
         N_("This is the certificate presented by %s:\n%s\n\n"
            "If in doubt, abort now.\n"),
305
306
                           N_("Abort"), N_("Accept 24 hours"),
                           N_("Accept permanently"), host, desc.data);
307
308
    gnutls_free (desc.data);

309
310
311
312
313
314
315
316
317
318
319
320
    time_t expiry = 0;
    switch (val)
    {
        case 2:
            time (&expiry);
            expiry += 24 * 60 * 60;
        case 3:
            gnutls_store_pubkey (NULL, NULL, host, service, GNUTLS_CRT_X509,
                                 datum, expiry, 0);
            return 0;
    }
    return -1;
321
322
323
}


324
static struct
325
326
{
    int flag;
327
328
    const char msg[43];
    bool strict;
329
} cert_errs[] =
330
331
{
    { GNUTLS_CERT_INVALID,
332
        "Certificate could not be verified", false },
333
    { GNUTLS_CERT_REVOKED,
334
        "Certificate was revoked", true },
335
    { GNUTLS_CERT_SIGNER_NOT_FOUND,
336
        "Certificate's signer was not found", false },
337
    { GNUTLS_CERT_SIGNER_NOT_CA,
338
        "Certificate's signer is not a CA", true },
339
    { GNUTLS_CERT_INSECURE_ALGORITHM,
340
      "Insecure certificate signature algorithm", true },
341
    { GNUTLS_CERT_NOT_ACTIVATED,
342
        "Certificate is not yet activated", true },
343
    { GNUTLS_CERT_EXPIRED,
344
        "Certificate has expired", true },
345
346
347
};


348
349
static int gnutls_HandshakeAndValidate (vlc_tls_t *session, const char *host,
                                        const char *service)
350
{
351
    vlc_tls_sys_t *sys = session->sys;
352

353
    int val = gnutls_ContinueHandshake (session, host, service);
354
355
    if (val)
        return val;
356

357
358
359
    /* certificates chain verification */
    unsigned status;

360
361
    val = gnutls_certificate_verify_peers2 (sys->session, &status);
    if (val)
362
    {
363
        msg_Err (session, "Certificate verification error: %s",
364
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
365
        return -1;
366
    }
367

368
    if (status)
369
    {
370
371
372
        msg_Err (session, "Certificate verification failure:");
        for (size_t i = 0; i < sizeof (cert_errs) / sizeof (cert_errs[0]); i++)
            if (status & cert_errs[i].flag)
373
            {
374
375
                msg_Err (session, " * %s", cert_errs[i].msg);
                status &= ~cert_errs[i].flag;
376
377
                if (cert_errs[i].strict)
                    val = -1;
378
            }
379

380
        if (status)
381
        {
382
            msg_Err (session, " * Unknown verification error 0x%04X", status);
383
384
385
            val = -1;
        }
        status = -1;
386
387
388
    }

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

    if (val || host == NULL)
        return val;
401
    if (status && gnutls_CertSearch (session, host, service, data))
402
        return -1;
403

404
    gnutls_x509_crt_t cert;
405
406
    val = gnutls_x509_crt_init (&cert);
    if (val)
407
    {
408
        msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
409
        return -1;
410
411
    }

412
413
    val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
    if (val)
414
    {
415
416
        msg_Err (session, "Certificate import error: %s",
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
417
        goto error;
418
    }
419

420
421
    val = !gnutls_x509_crt_check_hostname (cert, host);
    if (val)
422
    {
423
        msg_Err (session, "Certificate does not match \"%s\"", host);
424
        val = gnutls_CertSearch (session, host, service, data);
425
426
    }
error:
427
428
    gnutls_x509_crt_init (&cert);
    return val ? -1 : 0;
429
430
}

431
432
433
static int
gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
{
434
435
436
    char *priorities = var_InheritString (obj, "gnutls-priorities");
    if (unlikely(priorities == NULL))
        return VLC_ENOMEM;
437

438
439
    const char *errp;
    int val = gnutls_priority_set_direct (session, priorities, &errp);
440
441
    if (val < 0)
    {
442
        msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
443
                 gnutls_strerror (val));
444
        val = VLC_EGENERIC;
445
    }
446
447
448
449
    else
        val = VLC_SUCCESS;
    free (priorities);
    return val;
450
451
}

452

453
/**
454
 * TLS credentials private data
455
 */
456
struct vlc_tls_creds_sys
457
{
458
    gnutls_certificate_credentials_t x509_cred;
459
    gnutls_dh_params_t dh_params; /* XXX: used for server only */
460
461
    int (*handshake) (vlc_tls_t *, const char *, const char *);
        /* ^^ XXX: useful for server only */
462
463
464
465
466
467
468
};


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

473
474
475
    if (sys->handshaked)
        gnutls_bye (sys->session, GNUTLS_SHUT_WR);
    gnutls_deinit (sys->session);
476

477
    free (sys);
478
    (void) crd;
479
480
481
}


482
483
484
/**
 * Initializes a server-side TLS session.
 */
485
static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
486
                               int type, int fd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
487
{
488
489
    vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
    if (unlikely(sys == NULL))
490
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
491

492
493
494
495
    session->sys = sys;
    session->sock.p_sys = session;
    session->sock.pf_send = gnutls_Send;
    session->sock.pf_recv = gnutls_Recv;
496
    session->handshake = crd->sys->handshake;
497
    sys->handshaked = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
498

499
    int val = gnutls_init (&sys->session, type);
500
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
501
    {
502
        msg_Err (session, "cannot initialize TLS session: %s",
503
504
                 gnutls_strerror (val));
        free (sys);
505
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
506
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
507

508
    if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
509
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
510

511
    val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
512
                                  crd->sys->x509_cred);
513
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
514
    {
515
        msg_Err (session, "cannot set TLS session credentials: %s",
516
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
517
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
518
519
    }

520
521
    gnutls_transport_set_ptr (sys->session,
                              (gnutls_transport_ptr_t)(intptr_t)fd);
522
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
523
524

error:
525
526
    gnutls_SessionClose (crd, session);
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
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
557
558
559
560
561
562
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
563

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

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

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

    /* enables peer's certificate verification */
598
    crd->sys->handshake = gnutls_HandshakeAndValidate;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
599
600
601
602
    return VLC_SUCCESS;
}


603
/**
604
 * Adds a Certificates Revocation List to be sent to TLS clients.
605
 *
606
 * @param path (UTF-8) path of the CRL file.
607
608
609
 *
 * @return -1 on error, 0 on success.
 */
610
static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
611
{
612
613
614
615
616
617
618
619
620
621
622
    block_t *block = block_FilePath (path);
    if (block == NULL)
    {
        msg_Err (crd, "cannot read CRL from %s: %m", path);
        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
623

624
625
626
    int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
                                                   GNUTLS_X509_FMT_PEM);
    block_Release (block);
627
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
628
    {
629
        msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
630
631
        return VLC_EGENERIC;
    }
632
    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
633
634
    return VLC_SUCCESS;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
635

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

637
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
638
 * Allocates a whole server's TLS credentials.
639
 */
640
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
641
642
643
{
    int val;

644
    if (gnutls_Init (VLC_OBJECT(crd)))
645
646
        return VLC_EGENERIC;

647
648
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
649
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
650

651
    crd->sys     = sys;
652
653
    crd->add_CA  = gnutls_AddCA;
    crd->add_CRL = gnutls_AddCRL;
654
    crd->open    = gnutls_ServerSessionOpen;
655
    crd->close   = gnutls_SessionClose;
656
    /* No certificate validation by default */
657
    sys->handshake  = gnutls_ContinueHandshake;
658

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

668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
    block_t *certblock = block_FilePath (cert);
    if (certblock == NULL)
    {
        msg_Err (crd, "cannot read certificate chain from %s: %m", cert);
        return VLC_EGENERIC;
    }

    block_t *keyblock = block_FilePath (key);
    if (keyblock == NULL)
    {
        msg_Err (crd, "cannot read private key from %s: %m", key);
        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,
692
                                                GNUTLS_X509_FMT_PEM);
693
694
    block_Release (keyblock);
    block_Release (certblock);
695
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
696
    {
697
        msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
698
        gnutls_certificate_free_credentials (sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
699
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
700
701
    }

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

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

725
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
726
727

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

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

740
    /* all sessions depending on the server are now deinitialized */
741
742
743
    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
744

745
    gnutls_Deinit (VLC_OBJECT(crd));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
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
799
800

/**
 * 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));
}