gnutls.c 21.8 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
 *****************************************************************************
4
 * Copyright (C) 2004-2017 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
24
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

25
#include <limits.h>
26
27
#include <stdlib.h>
#include <string.h>
28
#include <time.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
29
#include <errno.h>
30
#include <assert.h>
31
#include <unistd.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
45
46
47
48
typedef struct vlc_tls_gnutls
{
    vlc_tls_t tls;
    gnutls_session_t session;
    vlc_object_t *obj;
} vlc_tls_gnutls_t;

49
50
51
52
53
54
55
56
static int gnutls_Init (vlc_object_t *obj)
{
    const char *version = gnutls_check_version ("3.3.0");
    if (version == NULL)
    {
        msg_Err (obj, "unsupported GnuTLS version");
        return -1;
    }
Benjamin Drung's avatar
Benjamin Drung committed
57
    msg_Dbg (obj, "using GnuTLS version %s", version);
58
59
60
    return 0;
}

61
static int gnutls_Error(vlc_tls_gnutls_t *priv, int val)
62
63
64
65
{
    switch (val)
    {
        case GNUTLS_E_AGAIN:
66
#ifdef _WIN32
67
            WSASetLastError (WSAEWOULDBLOCK);
68
#endif
69
            errno = EAGAIN;
70
            break;
71
72

        case GNUTLS_E_INTERRUPTED:
73
#ifdef _WIN32
74
            WSASetLastError (WSAEINTR);
75
#endif
76
            errno = EINTR;
77
78
79
            break;

        default:
80
            msg_Err(priv->obj, "%s", gnutls_strerror (val));
81
#ifndef NDEBUG
82
            if (!gnutls_error_is_fatal (val))
83
                msg_Err(priv->obj, "Error above should be handled");
84
#endif
85
#ifdef _WIN32
86
            WSASetLastError (WSAECONNRESET);
87
#endif
88
            errno = ECONNRESET;
89
90
91
92
    }
    return -1;
}

93
94
95
static ssize_t vlc_gnutls_read(gnutls_transport_ptr_t ptr, void *buf,
                               size_t length)
{
96
    vlc_tls_t *sock = ptr;
97
98
99
100
101
    struct iovec iov = {
        .iov_base = buf,
        .iov_len = length,
    };

102
    return sock->readv(sock, &iov, 1);
103
104
105
106
}

static ssize_t vlc_gnutls_writev(gnutls_transport_ptr_t ptr,
                                 const giovec_t *giov, int iovcnt)
107
{
108
109
110
111
112
113
#ifdef IOV_MAX
    const long iovmax = IOV_MAX;
#else
    const long iovmax = sysconf(_SC_IOV_MAX);
#endif
    if (unlikely(iovcnt > iovmax))
114
115
116
117
118
119
120
    {
        errno = EINVAL;
        return -1;
    }
    if (unlikely(iovcnt == 0))
        return 0;

121
    vlc_tls_t *sock = ptr;
122
123
124
125
126
127
128
129
    struct iovec iov[iovcnt];

    for (int i = 0; i < iovcnt; i++)
    {
        iov[i].iov_base = giov[i].iov_base;
        iov[i].iov_len = giov[i].iov_len;
    }

130
    return sock->writev(sock, iov, iovcnt);
131
132
}

133
134
static int gnutls_GetFD(vlc_tls_t *tls)
{
135
136
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;
    vlc_tls_t *sock = gnutls_transport_get_ptr(priv->session);
137

138
    return vlc_tls_GetFD(sock);
139
140
}

141
142
static ssize_t gnutls_Recv(vlc_tls_t *tls, struct iovec *iov, unsigned count)
{
143
144
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;
    gnutls_session_t session = priv->session;
145
146
147
148
149
150
    size_t rcvd = 0;

    while (count > 0)
    {
        ssize_t val = gnutls_record_recv(session, iov->iov_base, iov->iov_len);
        if (val < 0)
151
            return rcvd ? (ssize_t)rcvd : gnutls_Error(priv, val);
152
153
154
155
156
157
158
159
160
161
162
163
164

        rcvd += val;

        if ((size_t)val < iov->iov_len)
            break;

        iov++;
        count--;
    }

    return rcvd;
}

165
166
static ssize_t gnutls_Send (vlc_tls_t *tls, const struct iovec *iov,
                            unsigned count)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
167
{
168
169
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;
    gnutls_session_t session = priv->session;
170
    ssize_t val;
171

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
    if (!gnutls_record_check_corked(session))
    {
        gnutls_record_cork(session);

        while (count > 0)
        {
            val = gnutls_record_send(session, iov->iov_base, iov->iov_len);
            if (val < (ssize_t)iov->iov_len)
                break;

            iov++;
            count--;
        }
    }

    val = gnutls_record_uncork(session, 0);
188
    return (val < 0) ? gnutls_Error(priv, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
189
190
}

191
192
static int gnutls_Shutdown(vlc_tls_t *tls, bool duplex)
{
193
194
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;
    gnutls_session_t session = priv->session;
195
196
197
198
199
    ssize_t val;

    /* Flush any pending data */
    val = gnutls_record_uncork(session, 0);
    if (val < 0)
200
        return gnutls_Error(priv, val);
201

202
203
    val = gnutls_bye(session, duplex ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
    if (val < 0)
204
        return gnutls_Error(priv, val);
205
206

    return 0;
207
208
}

209
210
static void gnutls_Close (vlc_tls_t *tls)
{
211
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;
212

213
214
    gnutls_deinit(priv->session);
    free(priv);
215
216
}

217
218
219
220
static vlc_tls_gnutls_t *gnutls_SessionOpen(vlc_tls_creds_t *creds, int type,
                                         gnutls_certificate_credentials_t x509,
                                           vlc_tls_t *sock,
                                           const char *const *alpn)
221
{
222
223
224
225
    vlc_tls_gnutls_t *priv = malloc(sizeof (*priv));
    if (unlikely(priv == NULL))
        return NULL;

226
    gnutls_session_t session;
227
228
229
    const char *errp;
    int val;

230
    type |= GNUTLS_NONBLOCK;
231
232
233
#if (GNUTLS_VERSION_NUMBER >= 0x030500)
    type |= GNUTLS_ENABLE_FALSE_START;
#endif
234
235

    val = gnutls_init(&session, type);
236
237
    if (val != 0)
    {
238
239
        msg_Err(creds, "cannot initialize TLS session: %s",
                gnutls_strerror(val));
240
241
        free(priv);
        return NULL;
242
243
    }

244
    char *priorities = var_InheritString(creds, "gnutls-priorities");
245
246
247
    if (unlikely(priorities == NULL))
        goto error;

248
    val = gnutls_priority_set_direct (session, priorities, &errp);
249
    if (val < 0)
250
251
        msg_Err(creds, "cannot set TLS priorities \"%s\": %s", errp,
                gnutls_strerror(val));
252
253
254
255
    free (priorities);
    if (val < 0)
        goto error;

256
    val = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509);
257
258
    if (val < 0)
    {
259
260
        msg_Err(creds, "cannot set TLS session credentials: %s",
                gnutls_strerror(val));
261
262
263
        goto error;
    }

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
    if (alpn != NULL)
    {
        gnutls_datum_t *protv = NULL;
        unsigned protc = 0;

        while (*alpn != NULL)
        {
            gnutls_datum_t *n = realloc(protv, sizeof (*protv) * (protc + 1));
            if (unlikely(n == NULL))
            {
                free(protv);
                goto error;
            }
            protv = n;

            protv[protc].data = (void *)*alpn;
            protv[protc].size = strlen(*alpn);
            protc++;
            alpn++;
        }

        val = gnutls_alpn_set_protocols (session, protv, protc, 0);
        free (protv);
287
    }
288

289
    gnutls_transport_set_ptr(session, sock);
290
291
    gnutls_transport_set_vec_push_function(session, vlc_gnutls_writev);
    gnutls_transport_set_pull_function(session, vlc_gnutls_read);
292
293
294
295
296
297

    priv->session = session;
    priv->obj = VLC_OBJECT(creds);

    vlc_tls_t *tls = &priv->tls;

298
    tls->get_fd = gnutls_GetFD;
299
    tls->readv = gnutls_Recv;
300
    tls->writev = gnutls_Send;
301
    tls->shutdown = gnutls_Shutdown;
302
    tls->close = gnutls_Close;
303
    return priv;
304
305

error:
306
    gnutls_deinit (session);
307
308
    free(priv);
    return NULL;
309
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
310

311
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
312
313
 * Starts or continues the TLS handshake.
 *
Rémi Denis-Courmont's avatar
Typos    
Rémi Denis-Courmont committed
314
 * @return -1 on fatal error, 0 on successful handshake completion,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
315
316
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
317
 */
318
319
static int gnutls_ContinueHandshake(vlc_tls_creds_t *crd,
                                    vlc_tls_gnutls_t *priv,
320
                                    char **restrict alp)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
321
{
322
    gnutls_session_t session = priv->session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
323
324
    int val;

325
#ifdef _WIN32
326
    WSASetLastError (0);
327
#endif
328
329
    do
    {
330
        val = gnutls_handshake (session);
331
        msg_Dbg(crd, "TLS handshake: %s", gnutls_strerror (val));
332
333
334
335

        switch (val)
        {
            case GNUTLS_E_SUCCESS:
336
                goto done;
337
338
339
            case GNUTLS_E_AGAIN:
            case GNUTLS_E_INTERRUPTED:
                /* I/O event: return to caller's poll() loop */
340
                return 1 + gnutls_record_get_direction (session);
341
        }
342
    }
343
    while (!gnutls_error_is_fatal (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
344

345
#ifdef _WIN32
346
    msg_Dbg(crd, "Winsock error %d", WSAGetLastError ());
347
#endif
348
    msg_Err(crd, "TLS handshake error: %s", gnutls_strerror (val));
349
    return -1;
350

351
    unsigned flags;
352
done:
353
#if (GNUTLS_VERSION_NUMBER >= 0x030500)
354
355
356
357
358
359
360
361
    flags = gnutls_session_get_flags(session);

    if (flags & GNUTLS_SFLAGS_SAFE_RENEGOTIATION)
        msg_Dbg(crd, " - safe renegotiation (RFC5746) enabled");
    if (flags & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
        msg_Dbg(crd, " - extended master secret (RFC7627) enabled");
    if (flags & GNUTLS_SFLAGS_ETM)
        msg_Dbg(crd, " - encrypt then MAC (RFC7366) enabled");
362
363
364
    if (flags & GNUTLS_SFLAGS_FALSE_START)
        msg_Dbg(crd, " - false start (RFC7918) enabled");
#endif
365

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    if (alp != NULL)
    {
        gnutls_datum_t datum;

        val = gnutls_alpn_get_selected_protocol (session, &datum);
        if (val == 0)
        {
            if (memchr (datum.data, 0, datum.size) != NULL)
                return -1; /* Other end is doing something fishy?! */

            *alp = strndup ((char *)datum.data, datum.size);
            if (unlikely(*alp == NULL))
                return -1;
        }
        else
            *alp = NULL;
    }
    return 0;
384
385
}

386
387
388
static vlc_tls_t *gnutls_ClientSessionOpen(vlc_tls_creds_t *crd,
                                           vlc_tls_t *sk, const char *hostname,
                                           const char *const *alpn)
389
{
390
391
392
393
394
    vlc_tls_gnutls_t *priv;

    priv = gnutls_SessionOpen(crd, GNUTLS_CLIENT, crd->sys, sk, alpn);
    if (priv == NULL)
        return NULL;
395

396
    gnutls_session_t session = priv->session;
397
398

    /* minimum DH prime bits */
399
    gnutls_dh_set_prime_bits (session, 1024);
400
401
402

    if (likely(hostname != NULL))
        /* fill Server Name Indication */
403
        gnutls_server_name_set (session, GNUTLS_NAME_DNS,
404
405
                                hostname, strlen (hostname));

406
    return &priv->tls;
407
408
}

409
410
411
static int gnutls_ClientHandshake(vlc_tls_creds_t *creds, vlc_tls_t *tls,
                                  const char *host, const char *service,
                                  char **restrict alp)
412
{
413
414
415
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;

    int val = gnutls_ContinueHandshake(creds, priv, alp);
416
417
418
419
    if (val)
        return val;

    /* certificates chain verification */
420
    gnutls_session_t session = priv->session;
421
422
    unsigned status;

423
    val = gnutls_certificate_verify_peers3 (session, host, &status);
424
425
    if (val)
    {
426
427
        msg_Err(creds, "Certificate verification error: %s",
                gnutls_strerror(val));
Thomas Guillem's avatar
Thomas Guillem committed
428
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
429
    }
430

431
    if (status == 0) /* Good certificate */
432
        return 0;
433

434
435
    /* Bad certificate */
    gnutls_datum_t desc;
436

437
    if (gnutls_certificate_verification_status_print(status,
438
                         gnutls_certificate_type_get (session), &desc, 0) == 0)
439
    {
440
        msg_Err(creds, "Certificate verification failure: %s", desc.data);
441
442
443
444
445
446
447
448
        gnutls_free (desc.data);
    }

    status &= ~GNUTLS_CERT_INVALID; /* always set / catch-all error */
    status &= ~GNUTLS_CERT_SIGNER_NOT_FOUND; /* unknown CA */
    status &= ~GNUTLS_CERT_UNEXPECTED_OWNER; /* mismatched hostname */

    if (status != 0 || host == NULL)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
449
        goto error; /* Really bad certificate */
450

451
    /* Look up mismatching certificate in store */
452
453
454
    const gnutls_datum_t *datum;
    unsigned count;

455
    datum = gnutls_certificate_get_peers (session, &count);
456
457
    if (datum == NULL || count == 0)
    {
458
        msg_Err(creds, "Peer certificate not available");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
459
        goto error;
460
461
    }

462
    msg_Dbg(creds, "%u certificate(s) in the list", count);
463
464
    val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
                                       GNUTLS_CRT_X509, datum, 0);
465
    const char *msg;
466
467
468
    switch (val)
    {
        case 0:
469
            msg_Dbg(creds, "certificate key match for %s", host);
470
            return 0;
471
        case GNUTLS_E_NO_CERTIFICATE_FOUND:
472
            msg_Dbg(creds, "no known certificates for %s", host);
473
474
475
            msg = N_("However the security certificate presented by the "
                "server is unknown and could not be authenticated by any "
                "trusted Certificate Authority.");
476
477
            break;
        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
478
            msg_Dbg(creds, "certificate keys mismatch for %s", host);
479
480
481
            msg = N_("However the security certificate presented by the "
                "server changed since the previous visit and was not "
                "authenticated by any trusted Certificate Authority. ");
482
483
            break;
        default:
484
485
            msg_Err(creds, "certificate key match error for %s: %s", host,
                    gnutls_strerror(val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
486
            goto error;
487
    }
488

489
    if (vlc_dialog_wait_question(creds, VLC_DIALOG_QUESTION_WARNING,
Thomas Guillem's avatar
Thomas Guillem committed
490
491
492
493
494
495
            _("Abort"), _("View certificate"), NULL,
            _("Insecure site"), 
            _("You attempted to reach %s. %s\n"
            "This problem may be stem from an attempt to breach your security, "
            "compromise your privacy, or a configuration error.\n\n"
            "If in doubt, abort now.\n"), host, vlc_gettext(msg)) != 1)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
496
        goto error;
497
498
499
500

    gnutls_x509_crt_t cert;

    if (gnutls_x509_crt_init (&cert))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
501
        goto error;
502
503
504
505
    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);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
506
        goto error;
507
508
509
    }
    gnutls_x509_crt_deinit (cert);

510
    val = vlc_dialog_wait_question(creds, VLC_DIALOG_QUESTION_WARNING,
Thomas Guillem's avatar
Thomas Guillem committed
511
512
513
514
            _("Abort"), _("Accept 24 hours"), _("Accept permanently"),
            _("Insecure site"),
            _("This is the certificate presented by %s:\n%s\n\n"
            "If in doubt, abort now.\n"), host, desc.data);
515
516
    gnutls_free (desc.data);

517
518
519
    time_t expiry = 0;
    switch (val)
    {
Thomas Guillem's avatar
Thomas Guillem committed
520
        case 1:
521
522
            time (&expiry);
            expiry += 24 * 60 * 60;
Thomas Guillem's avatar
Thomas Guillem committed
523
        case 2:
524
525
526
            val = gnutls_store_pubkey (NULL, NULL, host, service,
                                       GNUTLS_CRT_X509, datum, expiry, 0);
            if (val)
527
                msg_Err(creds, "cannot store X.509 certificate: %s",
528
                         gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
529
530
531
            break;
        default:
            goto error;
532
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
533
    return 0;
Thomas Guillem's avatar
Thomas Guillem committed
534
535
536
537
538

error:
    if (alp != NULL)
        free(*alp);
    return -1;
539
540
}

541
/**
542
 * Initializes a client-side TLS credentials.
543
 */
544
static int OpenClient (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
545
{
546
    gnutls_certificate_credentials_t x509;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
547

548
549
    if (gnutls_Init (VLC_OBJECT(crd)))
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
550

551
    int val = gnutls_certificate_allocate_credentials (&x509);
552
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
553
    {
554
        msg_Err (crd, "cannot allocate credentials: %s",
555
                 gnutls_strerror (val));
556
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
557
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
558

559
560
561
562
563
564
565
566
567
    if (var_InheritBool(crd, "gnutls-system-trust"))
    {
        val = gnutls_certificate_set_x509_system_trust(x509);
        if (val < 0)
            msg_Err(crd, "cannot load trusted Certificate Authorities "
                    "from %s: %s", "system", gnutls_strerror(val));
        else
            msg_Dbg(crd, "loaded %d trusted CAs from %s", val, "system");
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
568

569
570
571
572
573
574
    gnutls_certificate_set_verify_flags (x509,
                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);

    crd->sys = x509;
    crd->open = gnutls_ClientSessionOpen;
    crd->handshake = gnutls_ClientHandshake;
575

576
    return VLC_SUCCESS;
577
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
578

579
580
581
582
583
static void CloseClient (vlc_tls_creds_t *crd)
{
    gnutls_certificate_credentials_t x509 = crd->sys;

    gnutls_certificate_free_credentials (x509);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
584
585
}

586
#ifdef ENABLE_SOUT
587
588
589
590
591
592
593
594
595
/**
 * Server-side TLS credentials private data
 */
typedef struct vlc_tls_creds_sys
{
    gnutls_certificate_credentials_t x509_cred;
    gnutls_dh_params_t dh_params;
} vlc_tls_creds_sys_t;

596
597
598
/**
 * Initializes a server-side TLS session.
 */
599
600
601
static vlc_tls_t *gnutls_ServerSessionOpen(vlc_tls_creds_t *crd,
                                           vlc_tls_t *sk, const char *hostname,
                                           const char *const *alpn)
602
{
603
    vlc_tls_creds_sys_t *sys = crd->sys;
604
    vlc_tls_gnutls_t *priv;
605

606
    assert (hostname == NULL);
607
608
    priv = gnutls_SessionOpen(crd, GNUTLS_SERVER, sys->x509_cred, sk, alpn);
    return (priv != NULL) ? &priv->tls : NULL;
609
610
}

611
612
613
static int gnutls_ServerHandshake(vlc_tls_creds_t *crd, vlc_tls_t *tls,
                                  const char *host, const char *service,
                                  char **restrict alp)
614
{
615
616
    vlc_tls_gnutls_t *priv = (vlc_tls_gnutls_t *)tls;

617
    (void) host; (void) service;
618
    return gnutls_ContinueHandshake(crd, priv, alp);
619
620
}

621
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
622
 * Allocates a whole server's TLS credentials.
623
 */
624
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
625
626
627
{
    int val;

628
    if (gnutls_Init (VLC_OBJECT(crd)))
629
630
        return VLC_EGENERIC;

631
632
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
633
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
634
635

    /* Sets server's credentials */
636
637
    val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
638
    {
639
        msg_Err (crd, "cannot allocate credentials: %s",
640
                 gnutls_strerror (val));
641
642
        free (sys);
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
643
644
    }

645
    block_t *certblock = block_FilePath(cert, false);
646
647
    if (certblock == NULL)
    {
648
649
        msg_Err (crd, "cannot read certificate chain from %s: %s", cert,
                 vlc_strerror_c(errno));
650
        goto error;
651
652
    }

653
    block_t *keyblock = block_FilePath(key, false);
654
655
    if (keyblock == NULL)
    {
656
657
        msg_Err (crd, "cannot read private key from %s: %s", key,
                 vlc_strerror_c(errno));
658
        block_Release (certblock);
659
        goto error;
660
661
662
663
664
665
666
667
668
669
670
    }

    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,
671
                                                GNUTLS_X509_FMT_PEM);
672
673
    block_Release (keyblock);
    block_Release (certblock);
674
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
675
    {
676
        msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
677
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
678
679
    }

680
    /* FIXME:
681
     * - regenerate these regularly
682
     * - support other cipher suites
683
     */
684
    val = gnutls_dh_params_init (&sys->dh_params);
685
686
    if (val >= 0)
    {
687
688
        gnutls_sec_param_t sec = GNUTLS_SEC_PARAM_MEDIUM;
        unsigned bits = gnutls_sec_param_to_pk_bits (GNUTLS_PK_DH, sec);
689

690
691
        msg_Dbg (crd, "generating Diffie-Hellman %u-bits parameters...", bits);
        val = gnutls_dh_params_generate2 (sys->dh_params, bits);
692
        if (val == 0)
693
694
            gnutls_certificate_set_dh_params (sys->x509_cred,
                                              sys->dh_params);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
695
    }
696
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
697
    {
698
        msg_Err (crd, "cannot initialize DHE cipher suites: %s",
699
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
700
701
    }

702
703
    msg_Dbg (crd, "ciphers parameters loaded");

704
705
    crd->sys = sys;
    crd->open = gnutls_ServerSessionOpen;
706
    crd->handshake = gnutls_ServerHandshake;
707

708
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
709
710

error:
711
    gnutls_certificate_free_credentials (sys->x509_cred);
712
    free (sys);
713
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
714
715
}

716
/**
717
 * Destroys a TLS server object.
718
 */
719
static void CloseServer (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
720
{
721
    vlc_tls_creds_sys_t *sys = crd->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
722

723
    /* all sessions depending on the server are now deinitialized */
724
725
726
    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
727
}
728
#endif
729

730
731
732
733
734
#define SYSTEM_TRUST_TEXT N_("Use system trust database")
#define SYSTEM_TRUST_LONGTEXT N_( \
    "Trust the root certificates of Certificate Authorities stored in " \
    "the operating system trust database to authenticate TLS sessions.")

735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
#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.")
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)"),
};
753

754
755
756
757
758
759
760
vlc_module_begin ()
    set_shortname( "GNU TLS" )
    set_description( N_("GNU TLS transport layer security") )
    set_capability( "tls client", 1 )
    set_callbacks( OpenClient, CloseClient )
    set_category( CAT_ADVANCED )
    set_subcategory( SUBCAT_ADVANCED_NETWORK )
761
762
    add_bool("gnutls-system-trust", true, SYSTEM_TRUST_TEXT,
             SYSTEM_TRUST_LONGTEXT, true)
763
764
765
766
767
768
769
770
771
772
773
774
    add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
                PRIORITIES_LONGTEXT, false)
        change_string_list (priorities_values, priorities_text)
#ifdef ENABLE_SOUT
    add_submodule ()
        set_description( N_("GNU TLS server") )
        set_capability( "tls server", 1 )
        set_category( CAT_ADVANCED )
        set_subcategory( SUBCAT_ADVANCED_NETWORK )
        set_callbacks( OpenServer, CloseServer )
#endif
vlc_module_end ()