gnutls.c 21.1 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-2015 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 49 50
#if (GNUTLS_VERSION_NUMBER >= 0x030300)
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
51
    msg_Dbg (obj, "using GnuTLS version %s", version);
52 53 54 55 56
    return 0;
}

# define gnutls_Deinit() (void)0
#else
57
#define GNUTLS_SEC_PARAM_MEDIUM GNUTLS_SEC_PARAM_NORMAL
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
58 59
static vlc_mutex_t gnutls_mutex = VLC_STATIC_MUTEX;

60 61 62 63
/**
 * Initializes GnuTLS with proper locking.
 * @return VLC_SUCCESS on success, a VLC error code otherwise.
 */
64
static int gnutls_Init (vlc_object_t *obj)
65
{
66
    const char *version = gnutls_check_version ("3.1.11");
67
    if (version == NULL)
68
    {
69 70
        msg_Err (obj, "unsupported GnuTLS version");
        return -1;
71
    }
Benjamin Drung's avatar
Benjamin Drung committed
72
    msg_Dbg (obj, "using GnuTLS version %s", version);
73

74
    if (gnutls_check_version ("3.3.0") == NULL)
75
    {
76
         int val;
77

78 79 80
         vlc_mutex_lock (&gnutls_mutex);
         val = gnutls_global_init ();
         vlc_mutex_unlock (&gnutls_mutex);
81

82 83 84 85 86 87 88
         if (val)
         {
             msg_Err (obj, "cannot initialize GnuTLS");
             return -1;
         }
    }
    return 0;
89 90 91 92 93
}

/**
 * Deinitializes GnuTLS.
 */
94
static void gnutls_Deinit (void)
95
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
96
    vlc_mutex_lock (&gnutls_mutex);
97
    gnutls_global_deinit ();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
98
    vlc_mutex_unlock (&gnutls_mutex);
99
}
100
#endif
101

102
static int gnutls_Error(vlc_tls_t *tls, int val)
103 104 105 106
{
    switch (val)
    {
        case GNUTLS_E_AGAIN:
107
#ifdef _WIN32
108
            WSASetLastError (WSAEWOULDBLOCK);
109
#endif
110
            errno = EAGAIN;
111
            break;
112 113

        case GNUTLS_E_INTERRUPTED:
114
#ifdef _WIN32
115
            WSASetLastError (WSAEINTR);
116
#endif
117
            errno = EINTR;
118 119 120
            break;

        default:
121
            msg_Err(tls->obj, "%s", gnutls_strerror (val));
122
#ifndef NDEBUG
123
            if (!gnutls_error_is_fatal (val))
124
                msg_Err(tls->obj, "Error above should be handled");
125
#endif
126
#ifdef _WIN32
127
            WSASetLastError (WSAECONNRESET);
128
#endif
129
            errno = ECONNRESET;
130 131 132 133
    }
    return -1;
}

134 135 136
static ssize_t vlc_gnutls_read(gnutls_transport_ptr_t ptr, void *buf,
                               size_t length)
{
137
    vlc_tls_t *sock = ptr;
138 139 140 141 142
    struct iovec iov = {
        .iov_base = buf,
        .iov_len = length,
    };

143
    return sock->readv(sock, &iov, 1);
144 145 146 147
}

static ssize_t vlc_gnutls_writev(gnutls_transport_ptr_t ptr,
                                 const giovec_t *giov, int iovcnt)
148
{
149 150 151 152 153 154
#ifdef IOV_MAX
    const long iovmax = IOV_MAX;
#else
    const long iovmax = sysconf(_SC_IOV_MAX);
#endif
    if (unlikely(iovcnt > iovmax))
155 156 157 158 159 160 161
    {
        errno = EINVAL;
        return -1;
    }
    if (unlikely(iovcnt == 0))
        return 0;

162
    vlc_tls_t *sock = ptr;
163 164 165 166 167 168 169 170
    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;
    }

171
    return sock->writev(sock, iov, iovcnt);
172 173
}

174 175 176
static int gnutls_GetFD(vlc_tls_t *tls)
{
    gnutls_session_t session = tls->sys;
177
    vlc_tls_t *sock = gnutls_transport_get_ptr(session);
178

179
    return vlc_tls_GetFD(sock);
180 181
}

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
static ssize_t gnutls_Recv(vlc_tls_t *tls, struct iovec *iov, unsigned count)
{
    gnutls_session_t session = tls->sys;
    size_t rcvd = 0;

    while (count > 0)
    {
        ssize_t val = gnutls_record_recv(session, iov->iov_base, iov->iov_len);
        if (val < 0)
            return rcvd ? (ssize_t)rcvd : gnutls_Error(tls, val);

        rcvd += val;

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

        iov++;
        count--;
    }

    return rcvd;
}

205 206
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
207
{
208
    gnutls_session_t session = tls->sys;
209
    ssize_t val;
210

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    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);
227
    return (val < 0) ? gnutls_Error (tls, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
228 229
}

230 231 232
static int gnutls_Shutdown(vlc_tls_t *tls, bool duplex)
{
    gnutls_session_t session = tls->sys;
233 234 235 236 237 238
    ssize_t val;

    /* Flush any pending data */
    val = gnutls_record_uncork(session, 0);
    if (val < 0)
        return gnutls_Error(tls, val);
239

240 241 242 243 244
    val = gnutls_bye(session, duplex ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
    if (val < 0)
        return gnutls_Error(tls, val);

    return 0;
245 246
}

247 248 249 250 251 252 253
static void gnutls_Close (vlc_tls_t *tls)
{
    gnutls_session_t session = tls->sys;

    gnutls_deinit (session);
}

254
static int gnutls_SessionOpen(vlc_tls_creds_t *creds, vlc_tls_t *tls, int type,
255 256
                              gnutls_certificate_credentials_t x509,
                              vlc_tls_t *sock, const char *const *alpn)
257
{
258
    gnutls_session_t session;
259 260 261
    const char *errp;
    int val;

262
    val = gnutls_init (&session, type);
263 264
    if (val != 0)
    {
265 266
        msg_Err(creds, "cannot initialize TLS session: %s",
                gnutls_strerror(val));
267 268 269
        return VLC_EGENERIC;
    }

270
    char *priorities = var_InheritString(creds, "gnutls-priorities");
271 272 273
    if (unlikely(priorities == NULL))
        goto error;

274
    val = gnutls_priority_set_direct (session, priorities, &errp);
275
    if (val < 0)
276 277
        msg_Err(creds, "cannot set TLS priorities \"%s\": %s", errp,
                gnutls_strerror(val));
278 279 280 281
    free (priorities);
    if (val < 0)
        goto error;

282
    val = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509);
283 284
    if (val < 0)
    {
285 286
        msg_Err(creds, "cannot set TLS session credentials: %s",
                gnutls_strerror(val));
287 288 289
        goto error;
    }

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
    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);
313
    }
314

315
    gnutls_transport_set_ptr(session, sock);
316 317
    gnutls_transport_set_vec_push_function(session, vlc_gnutls_writev);
    gnutls_transport_set_pull_function(session, vlc_gnutls_read);
318
    tls->sys = session;
319
    tls->get_fd = gnutls_GetFD;
320
    tls->readv = gnutls_Recv;
321
    tls->writev = gnutls_Send;
322
    tls->shutdown = gnutls_Shutdown;
323
    tls->close = gnutls_Close;
324 325 326
    return VLC_SUCCESS;

error:
327
    gnutls_deinit (session);
328 329
    return VLC_EGENERIC;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
330

331
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
332 333
 * Starts or continues the TLS handshake.
 *
Rémi Denis-Courmont's avatar
Typos  
Rémi Denis-Courmont committed
334
 * @return -1 on fatal error, 0 on successful handshake completion,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
335 336
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
337
 */
338 339
static int gnutls_ContinueHandshake(vlc_tls_creds_t *crd, vlc_tls_t *tls,
                                    char **restrict alp)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
340
{
341
    gnutls_session_t session = tls->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
342 343
    int val;

344
#ifdef _WIN32
345
    WSASetLastError (0);
346
#endif
347 348
    do
    {
349
        val = gnutls_handshake (session);
350
        msg_Dbg(crd, "TLS handshake: %s", gnutls_strerror (val));
351 352 353 354

        switch (val)
        {
            case GNUTLS_E_SUCCESS:
355
                goto done;
356 357 358
            case GNUTLS_E_AGAIN:
            case GNUTLS_E_INTERRUPTED:
                /* I/O event: return to caller's poll() loop */
359
                return 1 + gnutls_record_get_direction (session);
360
        }
361
    }
362
    while (!gnutls_error_is_fatal (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
363

364
#ifdef _WIN32
365
    msg_Dbg(crd, "Winsock error %d", WSAGetLastError ());
366
#endif
367
    msg_Err(crd, "TLS handshake error: %s", gnutls_strerror (val));
368
    return -1;
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388

done:
    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;
389 390
}

391 392 393
static int gnutls_ClientSessionOpen(vlc_tls_creds_t *crd, vlc_tls_t *tls,
                                    vlc_tls_t *sk, const char *hostname,
                                    const char *const *alpn)
394
{
395
    int val = gnutls_SessionOpen(crd, tls, GNUTLS_CLIENT, crd->sys, sk, alpn);
396 397 398
    if (val != VLC_SUCCESS)
        return val;

399
    gnutls_session_t session = tls->sys;
400 401

    /* minimum DH prime bits */
402
    gnutls_dh_set_prime_bits (session, 1024);
403 404 405

    if (likely(hostname != NULL))
        /* fill Server Name Indication */
406
        gnutls_server_name_set (session, GNUTLS_NAME_DNS,
407 408 409 410 411
                                hostname, strlen (hostname));

    return VLC_SUCCESS;
}

412 413 414
static int gnutls_ClientHandshake(vlc_tls_creds_t *creds, vlc_tls_t *tls,
                                  const char *host, const char *service,
                                  char **restrict alp)
415
{
416
    int val = gnutls_ContinueHandshake(creds, tls, alp);
417 418 419 420
    if (val)
        return val;

    /* certificates chain verification */
421
    gnutls_session_t session = tls->sys;
422 423
    unsigned status;

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

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

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

438
    if (gnutls_certificate_verification_status_print(status,
439
                         gnutls_certificate_type_get (session), &desc, 0) == 0)
440
    {
441
        msg_Err(creds, "Certificate verification failure: %s", desc.data);
442 443 444 445 446 447 448 449
        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
450
        goto error; /* Really bad certificate */
451

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

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

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

490
    if (vlc_dialog_wait_question(creds, VLC_DIALOG_QUESTION_WARNING,
Thomas Guillem's avatar
Thomas Guillem committed
491 492 493 494 495 496
            _("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
497
        goto error;
498 499 500 501

    gnutls_x509_crt_t cert;

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

511
    val = vlc_dialog_wait_question(creds, VLC_DIALOG_QUESTION_WARNING,
Thomas Guillem's avatar
Thomas Guillem committed
512 513 514 515
            _("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);
516 517
    gnutls_free (desc.data);

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

error:
    if (alp != NULL)
        free(*alp);
    return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
540 541
}

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

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

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

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

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

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

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

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

    gnutls_certificate_free_credentials (x509);
583
    gnutls_Deinit ();
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 int gnutls_ServerSessionOpen(vlc_tls_creds_t *crd, vlc_tls_t *tls,
                                    vlc_tls_t *sock, const char *hostname,
                                    const char *const *alpn)
602
{
603 604
    vlc_tls_creds_sys_t *sys = crd->sys;

605
    assert (hostname == NULL);
606
    return gnutls_SessionOpen(crd, tls, GNUTLS_SERVER, sys->x509_cred, sock,
607
                              alpn);
608 609
}

610 611 612
static int gnutls_ServerHandshake(vlc_tls_creds_t *crd, vlc_tls_t *tls,
                                  const char *host, const char *service,
                                  char **restrict alp)
613
{
614
    (void) host; (void) service;
615
    return gnutls_ContinueHandshake(crd, tls, alp);
616 617
}

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

625
    if (gnutls_Init (VLC_OBJECT(crd)))
626 627
        return VLC_EGENERIC;

628 629
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
630 631 632 633
    {
        gnutls_Deinit ();
        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 643
        free (sys);
        gnutls_Deinit ();
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
644 645
    }

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

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

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

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

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

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

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

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

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

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

725
    /* all sessions depending on the server are now deinitialized */
726 727 728
    gnutls_certificate_free_credentials (sys->x509_cred);
    gnutls_dh_params_deinit (sys->dh_params);
    free (sys);
729
    gnutls_Deinit ();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
730
}
731
#endif
732

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

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
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 )
    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 ()