gnutls.c 23 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
    set_capability( "tls client", 1 )
    set_callbacks( OpenClient, CloseClient )
    set_category( CAT_ADVANCED )
86
    set_subcategory( SUBCAT_ADVANCED_NETWORK )
87 88

    add_submodule ()
89
        set_description( N_("GNU TLS server") )
90 91
        set_capability( "tls server", 1 )
        set_category( CAT_ADVANCED )
92
        set_subcategory( SUBCAT_ADVANCED_NETWORK )
93 94
        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
    /* Look up mismatching certificate in store */
    int val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
                                           GNUTLS_CRT_X509, datum, 0);
264
    const char *msg;
265 266 267 268 269 270 271
    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);
272 273 274
            msg = N_("You attempted to reach %s. "
                "However the security certificate presented by the server "
                "is unknown and could not be authenticated by any trusted "
Pierre Ynard's avatar
Pierre Ynard committed
275
                "Certification Authority. "
276 277 278
                "This problem may be caused by a configuration error "
                "or an attempt to breach your security or your privacy.\n\n"
                "If in doubt, abort now.\n");
279 280 281
            break;
        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
            msg_Dbg (obj, "certificate keys mismatch for %s", host);
282 283 284
            msg = N_("You attempted to reach %s. "
                "However the security certificate presented by the server "
                "changed since the previous visit "
Pierre Ynard's avatar
Pierre Ynard committed
285 286
                "and was not authenticated by any trusted "
                "Certification Authority. "
287 288 289
                "This problem may be caused by a configuration error "
                "or an attempt to breach your security or your privacy.\n\n"
                "If in doubt, abort now.\n");
290 291 292 293 294 295
            break;
        default:
            msg_Err (obj, "certificate key match error for %s: %s", host,
                     gnutls_strerror (val));
            return -1;
    }
296

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

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

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

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


339
static struct
340 341
{
    int flag;
342 343
    const char msg[43];
    bool strict;
344
} cert_errs[] =
345 346
{
    { GNUTLS_CERT_INVALID,
347
        "Certificate could not be verified", false },
348
    { GNUTLS_CERT_REVOKED,
349
        "Certificate was revoked", true },
350
    { GNUTLS_CERT_SIGNER_NOT_FOUND,
351
        "Certificate's signer was not found", false },
352
    { GNUTLS_CERT_SIGNER_NOT_CA,
353
        "Certificate's signer is not a CA", true },
354
    { GNUTLS_CERT_INSECURE_ALGORITHM,
355
      "Insecure certificate signature algorithm", true },
356
    { GNUTLS_CERT_NOT_ACTIVATED,
357
        "Certificate is not yet activated", true },
358
    { GNUTLS_CERT_EXPIRED,
359
        "Certificate has expired", true },
360 361 362
};


363 364
static int gnutls_HandshakeAndValidate (vlc_tls_t *session, const char *host,
                                        const char *service)
365
{
366
    vlc_tls_sys_t *sys = session->sys;
367

368
    int val = gnutls_ContinueHandshake (session, host, service);
369 370
    if (val)
        return val;
371

372 373 374
    /* certificates chain verification */
    unsigned status;

375 376
    val = gnutls_certificate_verify_peers2 (sys->session, &status);
    if (val)
377
    {
378
        msg_Err (session, "Certificate verification error: %s",
379
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
380
        return -1;
381
    }
382

383
    if (status)
384
    {
385 386 387
        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)
388
            {
389 390
                msg_Err (session, " * %s", cert_errs[i].msg);
                status &= ~cert_errs[i].flag;
391 392
                if (cert_errs[i].strict)
                    val = -1;
393
            }
394

395
        if (status)
396
        {
397
            msg_Err (session, " * Unknown verification error 0x%04X", status);
398 399 400
            val = -1;
        }
        status = -1;
401 402 403
    }

    /* certificate (host)name verification */
404
    const gnutls_datum_t *data;
405 406 407
    unsigned count;
    data = gnutls_certificate_get_peers (sys->session, &count);
    if (data == NULL || count == 0)
408
    {
409
        msg_Err (session, "Peer certificate not available");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
410
        return -1;
411
    }
412 413 414 415
    msg_Dbg (session, "%u certificate(s) in the list", count);

    if (val || host == NULL)
        return val;
416
    if (status && gnutls_CertSearch (session, host, service, data))
417
        return -1;
418

419
    gnutls_x509_crt_t cert;
420 421
    val = gnutls_x509_crt_init (&cert);
    if (val)
422
    {
423
        msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
424
        return -1;
425 426
    }

427 428
    val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
    if (val)
429
    {
430 431
        msg_Err (session, "Certificate import error: %s",
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
432
        goto error;
433
    }
434

435 436
    val = !gnutls_x509_crt_check_hostname (cert, host);
    if (val)
437
    {
438
        msg_Err (session, "Certificate does not match \"%s\"", host);
439
        val = gnutls_CertSearch (session, host, service, data);
440 441
    }
error:
442 443
    gnutls_x509_crt_init (&cert);
    return val ? -1 : 0;
444 445
}

446 447 448
static int
gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
{
449 450 451
    char *priorities = var_InheritString (obj, "gnutls-priorities");
    if (unlikely(priorities == NULL))
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
452

453 454
    const char *errp;
    int val = gnutls_priority_set_direct (session, priorities, &errp);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
455 456
    if (val < 0)
    {
457
        msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
458
                 gnutls_strerror (val));
459
        val = VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
460
    }
461 462 463 464
    else
        val = VLC_SUCCESS;
    free (priorities);
    return val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
465 466
}

467

468
/**
469
 * TLS credentials private data
470
 */
471
struct vlc_tls_creds_sys
472
{
473
    gnutls_certificate_credentials_t x509_cred;
474
    gnutls_dh_params_t dh_params; /* XXX: used for server only */
475 476
    int (*handshake) (vlc_tls_t *, const char *, const char *);
        /* ^^ XXX: useful for server only */
477 478 479 480 481 482 483
};


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

488 489 490
    if (sys->handshaked)
        gnutls_bye (sys->session, GNUTLS_SHUT_WR);
    gnutls_deinit (sys->session);
491

492
    free (sys);
493
    (void) crd;
494 495 496
}


497 498 499
/**
 * Initializes a server-side TLS session.
 */
500
static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
501
                               int type, int fd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
502
{
503 504
    vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
    if (unlikely(sys == NULL))
505
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
506

507 508 509 510
    session->sys = sys;
    session->sock.p_sys = session;
    session->sock.pf_send = gnutls_Send;
    session->sock.pf_recv = gnutls_Recv;
511
    session->handshake = crd->sys->handshake;
512
    sys->handshaked = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
513

514
    int val = gnutls_init (&sys->session, type);
515
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
516
    {
517
        msg_Err (session, "cannot initialize TLS session: %s",
518 519
                 gnutls_strerror (val));
        free (sys);
520
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
521
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
522

523
    if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
524
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
525

526
    val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
527
                                  crd->sys->x509_cred);
528
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
529
    {
530
        msg_Err (session, "cannot set TLS session credentials: %s",
531
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
532
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
533 534
    }

535 536
    gnutls_transport_set_ptr (sys->session,
                              (gnutls_transport_ptr_t)(intptr_t)fd);
537
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
538 539

error:
540 541
    gnutls_SessionClose (crd, session);
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
542 543
}

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
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
578

579
/**
580
 * Adds one or more Certificate Authorities to the trusted set.
581
 *
582
 * @param path (UTF-8) path to an X.509 certificates list.
583 584
 *
 * @return -1 on error, 0 on success.
585
 */
586
static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
587
{
588 589 590 591 592 593 594 595 596 597 598
    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,
    };
599

600 601 602
    int val = gnutls_certificate_set_x509_trust_mem (crd->sys->x509_cred, &d,
                                                     GNUTLS_X509_FMT_PEM);
    block_Release (block);
603
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
604
    {
605
        msg_Err (crd, "cannot load trusted CA from %s: %s", path,
606
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
607 608
        return VLC_EGENERIC;
    }
609 610
    msg_Dbg (crd, " %d trusted CA%s added from %s", val, (val != 1) ? "s" : "",
             path);
611 612

    /* enables peer's certificate verification */
613
    crd->sys->handshake = gnutls_HandshakeAndValidate;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
614 615 616 617
    return VLC_SUCCESS;
}


618
/**
619
 * Adds a Certificates Revocation List to be sent to TLS clients.
620
 *
621
 * @param path (UTF-8) path of the CRL file.
622 623 624
 *
 * @return -1 on error, 0 on success.
 */
625
static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
626
{
627 628 629 630 631 632 633 634 635 636 637
    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
638

639 640 641
    int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
                                                   GNUTLS_X509_FMT_PEM);
    block_Release (block);
642
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
643
    {
644
        msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
645 646
        return VLC_EGENERIC;
    }
647
    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
648 649
    return VLC_SUCCESS;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
650

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

652
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
653
 * Allocates a whole server's TLS credentials.
654
 */
655
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
656 657 658
{
    int val;

659
    if (gnutls_Init (VLC_OBJECT(crd)))
660 661
        return VLC_EGENERIC;

662 663
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
664
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
665

666
    crd->sys     = sys;
667 668
    crd->add_CA  = gnutls_AddCA;
    crd->add_CRL = gnutls_AddCRL;
669
    crd->open    = gnutls_ServerSessionOpen;
670
    crd->close   = gnutls_SessionClose;
671
    /* No certificate validation by default */
672
    sys->handshake  = gnutls_ContinueHandshake;
673

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
674
    /* Sets server's credentials */
675 676
    val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
677
    {
678
        msg_Err (crd, "cannot allocate credentials: %s",
679
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
680
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
681 682
    }

683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
    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,
707
                                                GNUTLS_X509_FMT_PEM);
708 709
    block_Release (keyblock);
    block_Release (certblock);
710
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
711
    {
712
        msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
713
        gnutls_certificate_free_credentials (sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
714
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
715 716
    }

717
    /* FIXME:
718
     * - support other cipher suites
719
     */
720
    val = gnutls_dh_params_init (&sys->dh_params);
721 722
    if (val >= 0)
    {
723 724 725 726 727
        const gnutls_datum_t data = {
            .data = (unsigned char *)dh_params,
            .size = sizeof (dh_params) - 1,
        };

728
        val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
729 730
                                             GNUTLS_X509_FMT_PEM);
        if (val == 0)
731 732
            gnutls_certificate_set_dh_params (sys->x509_cred,
                                              sys->dh_params);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
733
    }
734
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
735
    {
736
        msg_Err (crd, "cannot initialize DHE cipher suites: %s",
737
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
738 739
    }

740
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
741 742

error:
743
    free (sys);
744
    gnutls_Deinit (VLC_OBJECT(crd));
745
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
746 747
}

748
/**
749
 * Destroys a TLS server object.
750
 */
751
static void CloseServer (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
752
{
753
    vlc_tls_creds_sys_t *sys = crd->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
754

755
    /* all sessions depending on the server are now deinitialized */
756 757 758
    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
759

760
    gnutls_Deinit (VLC_OBJECT(crd));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
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 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815

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