gnutls.c 19.9 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

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

#include <gnutls/gnutls.h>
39
#include <gnutls/x509.h>
40

41 42 43 44 45 46 47 48 49
#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
50
    msg_Dbg (obj, "using GnuTLS version %s", version);
51 52 53 54 55
    return 0;
}

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

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

72
    if (gnutls_check_version ("3.3.0") == NULL)
73
    {
74
         int val;
75

76 77 78
         vlc_mutex_lock (&gnutls_mutex);
         val = gnutls_global_init ();
         vlc_mutex_unlock (&gnutls_mutex);
79

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

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

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

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

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

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
#ifdef IOV_MAX
static ssize_t vlc_gnutls_writev (gnutls_transport_ptr_t ptr,
                                  const giovec_t *giov, int iovcnt)
{
    if (unlikely((unsigned)iovcnt > IOV_MAX))
    {
        errno = EINVAL;
        return -1;
    }
    if (unlikely(iovcnt == 0))
        return 0;

    struct iovec iov[iovcnt];
    struct msghdr msg = {
        .msg_iov = iov,
        .msg_iovlen = iovcnt,
    };
    int fd = (intptr_t)ptr;

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

    return sendmsg (fd, &msg, MSG_NOSIGNAL);
}
#endif

162
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
163
 * Sends data through a TLS session.
164
 */
165
static int gnutls_Send (void *opaque, const void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
166
{
167 168 169 170
    assert (opaque != NULL);

    vlc_tls_t *tls = opaque;
    gnutls_session_t session = tls->sys;
171

172 173
    int val = gnutls_record_send (session, buf, length);
    return (val < 0) ? gnutls_Error (tls, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
174 175 176
}


177
/**
178
 * Receives data through a TLS session.
179
 */
180
static int gnutls_Recv (void *opaque, void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
181
{
182
    assert (opaque != NULL);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
183

184 185
    vlc_tls_t *tls = opaque;
    gnutls_session_t session = tls->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
186

187 188
    int val = gnutls_record_recv (session, buf, length);
    return (val < 0) ? gnutls_Error (tls, val) : val;
189 190 191
}

static int gnutls_SessionOpen (vlc_tls_t *tls, int type,
192 193
                               gnutls_certificate_credentials_t x509, int fd,
                               const char *const *alpn)
194
{
195
    gnutls_session_t session;
196 197 198
    const char *errp;
    int val;

199
    val = gnutls_init (&session, type);
200 201 202 203 204 205 206 207 208 209 210
    if (val != 0)
    {
        msg_Err (tls, "cannot initialize TLS session: %s",
                 gnutls_strerror (val));
        return VLC_EGENERIC;
    }

    char *priorities = var_InheritString (tls, "gnutls-priorities");
    if (unlikely(priorities == NULL))
        goto error;

211
    val = gnutls_priority_set_direct (session, priorities, &errp);
212 213 214 215 216 217 218
    if (val < 0)
        msg_Err (tls, "cannot set TLS priorities \"%s\": %s", errp,
                 gnutls_strerror (val));
    free (priorities);
    if (val < 0)
        goto error;

219
    val = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509);
220 221 222 223 224 225 226
    if (val < 0)
    {
        msg_Err (tls, "cannot set TLS session credentials: %s",
                 gnutls_strerror (val));
        goto error;
    }

227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
    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);
250
    }
251

252
    gnutls_transport_set_int (session, fd);
253 254 255
#ifdef IOV_MAX
    gnutls_transport_set_vec_push_function (session, vlc_gnutls_writev);
#endif
256 257
    tls->sys = session;
    tls->sock.p_sys = NULL;
258 259 260 261 262
    tls->sock.pf_send = gnutls_Send;
    tls->sock.pf_recv = gnutls_Recv;
    return VLC_SUCCESS;

error:
263
    gnutls_deinit (session);
264 265
    return VLC_EGENERIC;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
266

267
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
268 269
 * Starts or continues the TLS handshake.
 *
Rémi Denis-Courmont's avatar
Typos  
Rémi Denis-Courmont committed
270
 * @return -1 on fatal error, 0 on successful handshake completion,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
271 272
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
273
 */
274
static int gnutls_ContinueHandshake (vlc_tls_t *tls, char **restrict alp)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
275
{
276
    gnutls_session_t session = tls->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
277 278
    int val;

279
#ifdef _WIN32
280
    WSASetLastError (0);
281
#endif
282 283
    do
    {
284
        val = gnutls_handshake (session);
285 286 287 288 289
        msg_Dbg (tls, "TLS handshake: %s", gnutls_strerror (val));

        switch (val)
        {
            case GNUTLS_E_SUCCESS:
290
                goto done;
291 292 293
            case GNUTLS_E_AGAIN:
            case GNUTLS_E_INTERRUPTED:
                /* I/O event: return to caller's poll() loop */
294
                return 1 + gnutls_record_get_direction (session);
295
        }
296
    }
297
    while (!gnutls_error_is_fatal (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
298

299
#ifdef _WIN32
300
    msg_Dbg (tls, "Winsock error %d", WSAGetLastError ());
301
#endif
302 303
    msg_Err (tls, "TLS handshake error: %s", gnutls_strerror (val));
    return -1;
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

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;
324 325
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339
/**
 * Terminates TLS session and releases session data.
 * You still have to close the socket yourself.
 */
static void gnutls_SessionClose (vlc_tls_t *tls)
{
    gnutls_session_t session = tls->sys;

    if (tls->sock.p_sys != NULL)
        gnutls_bye (session, GNUTLS_SHUT_WR);

    gnutls_deinit (session);
}

340
static int gnutls_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
341 342
                                     int fd, const char *hostname,
                                     const char *const *alpn)
343
{
344
    int val = gnutls_SessionOpen (tls, GNUTLS_CLIENT, crd->sys, fd, alpn);
345 346 347
    if (val != VLC_SUCCESS)
        return val;

348
    gnutls_session_t session = tls->sys;
349 350

    /* minimum DH prime bits */
351
    gnutls_dh_set_prime_bits (session, 1024);
352 353 354

    if (likely(hostname != NULL))
        /* fill Server Name Indication */
355
        gnutls_server_name_set (session, GNUTLS_NAME_DNS,
356 357 358 359 360 361
                                hostname, strlen (hostname));

    return VLC_SUCCESS;
}

static int gnutls_ClientHandshake (vlc_tls_t *tls, const char *host,
362
                                   const char *service, char **restrict alp)
363
{
364
    int val = gnutls_ContinueHandshake (tls, alp);
365 366 367 368
    if (val)
        return val;

    /* certificates chain verification */
369
    gnutls_session_t session = tls->sys;
370 371
    unsigned status;

372
    val = gnutls_certificate_verify_peers3 (session, host, &status);
373 374 375 376
    if (val)
    {
        msg_Err (tls, "Certificate verification error: %s",
                 gnutls_strerror (val));
377 378
failure:
        gnutls_bye (session, GNUTLS_SHUT_RDWR);
379
        return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
380
    }
381

382
    if (status == 0)
383 384 385 386 387
    {   /* Good certificate */
success:
        tls->sock.p_sys = tls;
        return 0;
    }
388

389 390
    /* Bad certificate */
    gnutls_datum_t desc;
391

392
    if (gnutls_certificate_verification_status_print(status,
393
                         gnutls_certificate_type_get (session), &desc, 0) == 0)
394 395 396 397 398 399 400 401 402 403
    {
        msg_Err (tls, "Certificate verification failure: %s", desc.data);
        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)
404
        goto failure; /* Really bad certificate */
405

406
    /* Look up mismatching certificate in store */
407 408 409
    const gnutls_datum_t *datum;
    unsigned count;

410
    datum = gnutls_certificate_get_peers (session, &count);
411 412 413
    if (datum == NULL || count == 0)
    {
        msg_Err (tls, "Peer certificate not available");
414
        goto failure;
415 416 417 418 419
    }

    msg_Dbg (tls, "%u certificate(s) in the list", count);
    val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
                                       GNUTLS_CRT_X509, datum, 0);
420
    const char *msg;
421 422 423
    switch (val)
    {
        case 0:
424 425
            msg_Dbg (tls, "certificate key match for %s", host);
            goto success;
426
        case GNUTLS_E_NO_CERTIFICATE_FOUND:
427 428 429 430
            msg_Dbg (tls, "no known certificates for %s", host);
            msg = N_("However the security certificate presented by the "
                "server is unknown and could not be authenticated by any "
                "trusted Certificate Authority.");
431 432
            break;
        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
433 434 435 436
            msg_Dbg (tls, "certificate keys mismatch for %s", host);
            msg = N_("However the security certificate presented by the "
                "server changed since the previous visit and was not "
                "authenticated by any trusted Certificate Authority. ");
437 438
            break;
        default:
439
            msg_Err (tls, "certificate key match error for %s: %s", host,
440
                     gnutls_strerror (val));
441
            goto failure;
442
    }
443

444 445 446 447 448 449 450
    if (dialog_Question (tls, _("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"),
                         _("Abort"), _("View certificate"), NULL,
                         vlc_gettext (msg), host) != 2)
451
        goto failure;
452 453 454 455

    gnutls_x509_crt_t cert;

    if (gnutls_x509_crt_init (&cert))
456
        goto failure;
457 458 459 460
    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);
461
        goto failure;
462 463 464
    }
    gnutls_x509_crt_deinit (cert);

465
    val = dialog_Question (tls, _("Insecure site"),
466 467 468 469
         _("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);
470 471
    gnutls_free (desc.data);

472 473 474 475 476 477 478
    time_t expiry = 0;
    switch (val)
    {
        case 2:
            time (&expiry);
            expiry += 24 * 60 * 60;
        case 3:
479 480 481
            val = gnutls_store_pubkey (NULL, NULL, host, service,
                                       GNUTLS_CRT_X509, datum, expiry, 0);
            if (val)
482
                msg_Err (tls, "cannot store X.509 certificate: %s",
483
                         gnutls_strerror (val));
484
            goto success;
485
    }
486
    goto failure;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
487 488
}

489
/**
490
 * Initializes a client-side TLS credentials.
491
 */
492
static int OpenClient (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
493
{
494
    gnutls_certificate_credentials_t x509;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
495

496 497
    if (gnutls_Init (VLC_OBJECT(crd)))
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
498

499
    int val = gnutls_certificate_allocate_credentials (&x509);
500
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
501
    {
502
        msg_Err (crd, "cannot allocate credentials: %s",
503
                 gnutls_strerror (val));
504
        gnutls_Deinit ();
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
    val = gnutls_certificate_set_x509_system_trust (x509);
509
    if (val < 0)
510
        msg_Err (crd, "cannot load trusted Certificate Authorities: %s",
511
                 gnutls_strerror (val));
512 513
    else
        msg_Dbg (crd, "loaded %d trusted CAs", val);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
514

515 516 517 518 519 520 521
    gnutls_certificate_set_verify_flags (x509,
                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);

    crd->sys = x509;
    crd->open = gnutls_ClientSessionOpen;
    crd->handshake = gnutls_ClientHandshake;
    crd->close = gnutls_SessionClose;
522

523
    return VLC_SUCCESS;
524
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
525

526 527 528 529 530
static void CloseClient (vlc_tls_creds_t *crd)
{
    gnutls_certificate_credentials_t x509 = crd->sys;

    gnutls_certificate_free_credentials (x509);
531
    gnutls_Deinit ();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
532 533
}

534
#ifdef ENABLE_SOUT
535 536 537 538 539 540 541 542 543
/**
 * 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;

544 545 546
/**
 * Initializes a server-side TLS session.
 */
547
static int gnutls_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *tls,
548 549
                                     int fd, const char *hostname,
                                     const char *const *alpn)
550
{
551 552
    vlc_tls_creds_sys_t *sys = crd->sys;

553
    assert (hostname == NULL);
554
    return gnutls_SessionOpen (tls, GNUTLS_SERVER, sys->x509_cred, fd, alpn);
555 556
}

557
static int gnutls_ServerHandshake (vlc_tls_t *tls, const char *host,
558
                                   const char *service, char **restrict alp)
559
{
560
    int val = gnutls_ContinueHandshake (tls, alp);
561
    if (val == 0)
562
        tls->sock.p_sys = tls;
563

564 565
    (void) host; (void) service;
    return val;
566 567
}

568
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
569
 * Allocates a whole server's TLS credentials.
570
 */
571
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
572 573 574
{
    int val;

575
    if (gnutls_Init (VLC_OBJECT(crd)))
576 577
        return VLC_EGENERIC;

578 579
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
580 581 582 583
    {
        gnutls_Deinit ();
        return VLC_ENOMEM;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
584 585

    /* Sets server's credentials */
586 587
    val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
588
    {
589
        msg_Err (crd, "cannot allocate credentials: %s",
590
                 gnutls_strerror (val));
591 592 593
        free (sys);
        gnutls_Deinit ();
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
594 595
    }

596 597 598
    block_t *certblock = block_FilePath (cert);
    if (certblock == NULL)
    {
599 600
        msg_Err (crd, "cannot read certificate chain from %s: %s", cert,
                 vlc_strerror_c(errno));
601
        goto error;
602 603 604 605 606
    }

    block_t *keyblock = block_FilePath (key);
    if (keyblock == NULL)
    {
607 608
        msg_Err (crd, "cannot read private key from %s: %s", key,
                 vlc_strerror_c(errno));
609
        block_Release (certblock);
610
        goto error;
611 612 613 614 615 616 617 618 619 620 621
    }

    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,
622
                                                GNUTLS_X509_FMT_PEM);
623 624
    block_Release (keyblock);
    block_Release (certblock);
625
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
626
    {
627
        msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
628
        gnutls_certificate_free_credentials (sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
629
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
630 631
    }

632
    /* FIXME:
633
     * - regenerate these regularly
634
     * - support other cipher suites
635
     */
636
    val = gnutls_dh_params_init (&sys->dh_params);
637 638
    if (val >= 0)
    {
639 640
        gnutls_sec_param_t sec = GNUTLS_SEC_PARAM_MEDIUM;
        unsigned bits = gnutls_sec_param_to_pk_bits (GNUTLS_PK_DH, sec);
641

642 643
        msg_Dbg (crd, "generating Diffie-Hellman %u-bits parameters...", bits);
        val = gnutls_dh_params_generate2 (sys->dh_params, bits);
644
        if (val == 0)
645 646
            gnutls_certificate_set_dh_params (sys->x509_cred,
                                              sys->dh_params);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
647
    }
648
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
649
    {
650
        msg_Err (crd, "cannot initialize DHE cipher suites: %s",
651
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
652 653
    }

654 655
    msg_Dbg (crd, "ciphers parameters loaded");

656 657
    crd->sys = sys;
    crd->open = gnutls_ServerSessionOpen;
658
    crd->handshake = gnutls_ServerHandshake;
659 660
    crd->close = gnutls_SessionClose;

661
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
662 663

error:
664
    gnutls_certificate_free_credentials (sys->x509_cred);
665
    free (sys);
666
    gnutls_Deinit ();
667
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
668 669
}

670
/**
671
 * Destroys a TLS server object.
672
 */
673
static void CloseServer (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
674
{
675
    vlc_tls_creds_sys_t *sys = crd->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
676

677
    /* all sessions depending on the server are now deinitialized */
678 679 680
    gnutls_certificate_free_credentials (sys->x509_cred);
    gnutls_dh_params_deinit (sys->dh_params);
    free (sys);
681
    gnutls_Deinit ();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
682
}
683
#endif
684

685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
#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)"),
};
703

704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
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 ()