gnutls.c 22.5 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
#include "dhparams.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43 44 45
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
46 47
static int  OpenClient  (vlc_tls_creds_t *);
static void CloseClient (vlc_tls_creds_t *);
48 49
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
50

51 52 53 54
#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.")
55 56 57 58 59 60 61 62 63 64 65 66 67 68
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)"),
};
69

70
vlc_module_begin ()
71 72
    set_shortname( "GNU TLS" )
    set_description( N_("GNU TLS transport layer security") )
73 74 75
    set_capability( "tls client", 1 )
    set_callbacks( OpenClient, CloseClient )
    set_category( CAT_ADVANCED )
76
    set_subcategory( SUBCAT_ADVANCED_NETWORK )
77 78

    add_submodule ()
79
        set_description( N_("GNU TLS server") )
80 81
        set_capability( "tls server", 1 )
        set_category( CAT_ADVANCED )
82
        set_subcategory( SUBCAT_ADVANCED_NETWORK )
83 84
        set_callbacks( OpenServer, CloseServer )

85 86
        add_string ("gnutls-priorities", "NORMAL", PRIORITIES_TEXT,
                    PRIORITIES_LONGTEXT, false)
87
            change_string_list (priorities_values, priorities_text)
88
vlc_module_end ()
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
89

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

92 93 94 95 96
/**
 * Initializes GnuTLS with proper locking.
 * @return VLC_SUCCESS on success, a VLC error code otherwise.
 */
static int gnutls_Init (vlc_object_t *p_this)
97
{
98 99
    int ret = VLC_EGENERIC;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
100
    vlc_mutex_lock (&gnutls_mutex);
101 102 103 104 105 106
    if (gnutls_global_init ())
    {
        msg_Err (p_this, "cannot initialize GnuTLS");
        goto error;
    }

107
    const char *psz_version = gnutls_check_version ("3.0.20");
108 109 110 111 112 113 114 115 116 117 118
    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
119
    vlc_mutex_unlock (&gnutls_mutex);
120 121 122 123 124 125 126 127 128
    return ret;
}


/**
 * Deinitializes GnuTLS.
 */
static void gnutls_Deinit (vlc_object_t *p_this)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129
    vlc_mutex_lock (&gnutls_mutex);
130 131 132

    gnutls_global_deinit ();
    msg_Dbg (p_this, "GnuTLS deinitialized");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
133
    vlc_mutex_unlock (&gnutls_mutex);
134
}
135 136


137 138 139 140 141
static int gnutls_Error (vlc_object_t *obj, int val)
{
    switch (val)
    {
        case GNUTLS_E_AGAIN:
142
#ifdef _WIN32
143 144
            WSASetLastError (WSAEWOULDBLOCK);
#else
145
            errno = EAGAIN;
146
#endif
147
            break;
148 149

        case GNUTLS_E_INTERRUPTED:
150
#ifdef _WIN32
151
            WSASetLastError (WSAEINTR);
152
#else
153
            errno = EINTR;
154
#endif
155 156 157 158
            break;

        default:
            msg_Err (obj, "%s", gnutls_strerror (val));
159
#ifndef NDEBUG
160 161 162
            if (!gnutls_error_is_fatal (val))
                msg_Err (obj, "Error above should be handled");
#endif
163
#ifdef _WIN32
164
            WSASetLastError (WSAECONNRESET);
165
#else
166
            errno = ECONNRESET;
167
#endif
168 169 170
    }
    return -1;
}
171
#define gnutls_Error(o, val) gnutls_Error(VLC_OBJECT(o), val)
172

173
struct vlc_tls_sys
174
{
175
    gnutls_session_t session;
176
    bool handshaked;
177 178 179
};


180
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
181
 * Sends data through a TLS session.
182
 */
183
static int gnutls_Send (void *opaque, const void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
184
{
185 186
    vlc_tls_t *session = opaque;
    vlc_tls_sys_t *sys = session->sys;
187

188 189
    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
190 191 192
}


193
/**
194
 * Receives data through a TLS session.
195
 */
196
static int gnutls_Recv (void *opaque, void *buf, size_t length)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
197
{
198 199
    vlc_tls_t *session = opaque;
    vlc_tls_sys_t *sys = session->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
200

201 202
    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
203 204 205
}


206
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
207 208
 * Starts or continues the TLS handshake.
 *
Rémi Denis-Courmont's avatar
Typos  
Rémi Denis-Courmont committed
209
 * @return -1 on fatal error, 0 on successful handshake completion,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
210 211
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
212
 */
213 214
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
215
{
216
    vlc_tls_sys_t *sys = session->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
217 218
    int val;

219
#ifdef _WIN32
220
    WSASetLastError (0);
221
#endif
222 223 224 225 226 227 228 229 230 231
    do
    {
        val = gnutls_handshake (sys->session);
        msg_Dbg (session, "TLS handshake: %s", gnutls_strerror (val));

        if ((val == GNUTLS_E_AGAIN) || (val == GNUTLS_E_INTERRUPTED))
            /* I/O event: return to caller's poll() loop */
            return 1 + gnutls_record_get_direction (sys->session);
    }
    while (val < 0 && !gnutls_error_is_fatal (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
232

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

242
    sys->handshaked = true;
243
    (void) host; (void) service;
244 245 246
    return 0;
}

247

248 249 250 251 252
/**
 * 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,
253
                              const char *service,
254 255 256
                              const gnutls_datum_t *restrict datum)
{
    assert (host != NULL);
257

258 259 260
    /* Look up mismatching certificate in store */
    int val = gnutls_verify_stored_pubkey (NULL, NULL, host, service,
                                           GNUTLS_CRT_X509, datum, 0);
261
    const char *msg;
262 263 264 265 266 267 268
    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);
269 270 271
            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
272
                "Certification Authority. "
273 274 275
                "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");
276 277 278
            break;
        case GNUTLS_E_CERTIFICATE_KEY_MISMATCH:
            msg_Dbg (obj, "certificate keys mismatch for %s", host);
279 280 281
            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
282 283
                "and was not authenticated by any trusted "
                "Certification Authority. "
284 285 286
                "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");
287 288 289 290 291 292
            break;
        default:
            msg_Err (obj, "certificate key match error for %s: %s", host,
                     gnutls_strerror (val));
            return -1;
    }
293

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

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

311 312 313 314 315
    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);
316 317
    gnutls_free (desc.data);

318 319 320 321 322 323 324
    time_t expiry = 0;
    switch (val)
    {
        case 2:
            time (&expiry);
            expiry += 24 * 60 * 60;
        case 3:
325 326 327 328 329
            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));
330 331 332
            return 0;
    }
    return -1;
333 334 335
}


336
static struct
337
{
338 339
    unsigned flag;
    const char msg[29];
340
} cert_errs[] =
341
{
342 343 344 345 346 347 348
    { GNUTLS_CERT_INVALID,            "Certificate not verified"     },
    { GNUTLS_CERT_REVOKED,            "Certificate revoked"          },
    { GNUTLS_CERT_SIGNER_NOT_FOUND,   "Signer not found"             },
    { GNUTLS_CERT_SIGNER_NOT_CA,      "Signer not a CA"              },
    { GNUTLS_CERT_INSECURE_ALGORITHM, "Signature algorithm insecure" },
    { GNUTLS_CERT_NOT_ACTIVATED,      "Certificate not activated"    },
    { GNUTLS_CERT_EXPIRED,            "Certificate expired"          },
349 350 351
};


352 353
static int gnutls_HandshakeAndValidate (vlc_tls_t *session, const char *host,
                                        const char *service)
354
{
355
    vlc_tls_sys_t *sys = session->sys;
356

357
    int val = gnutls_ContinueHandshake (session, host, service);
358 359
    if (val)
        return val;
360

361 362 363
    /* certificates chain verification */
    unsigned status;

364 365
    val = gnutls_certificate_verify_peers2 (sys->session, &status);
    if (val)
366
    {
367
        msg_Err (session, "Certificate verification error: %s",
368
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
369
        return -1;
370
    }
371
    if (status)
372
    {
373
        msg_Err (session, "Certificate verification failure (0x%04X)", status);
374 375 376
        for (size_t i = 0; i < sizeof (cert_errs) / sizeof (cert_errs[0]); i++)
            if (status & cert_errs[i].flag)
                msg_Err (session, " * %s", cert_errs[i].msg);
377 378
        if (status & ~(GNUTLS_CERT_INVALID|GNUTLS_CERT_SIGNER_NOT_FOUND))
            return -1;
379 380 381
    }

    /* certificate (host)name verification */
382
    const gnutls_datum_t *data;
383 384 385
    unsigned count;
    data = gnutls_certificate_get_peers (sys->session, &count);
    if (data == NULL || count == 0)
386
    {
387
        msg_Err (session, "Peer certificate not available");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
388
        return -1;
389
    }
390 391 392 393
    msg_Dbg (session, "%u certificate(s) in the list", count);

    if (val || host == NULL)
        return val;
394
    if (status && gnutls_CertSearch (session, host, service, data))
395
        return -1;
396

397
    gnutls_x509_crt_t cert;
398 399
    val = gnutls_x509_crt_init (&cert);
    if (val)
400
    {
401
        msg_Err (session, "X.509 fatal error: %s", gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
402
        return -1;
403 404
    }

405 406
    val = gnutls_x509_crt_import (cert, data, GNUTLS_X509_FMT_DER);
    if (val)
407
    {
408 409
        msg_Err (session, "Certificate import error: %s",
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
410
        goto error;
411
    }
412

413 414
    val = !gnutls_x509_crt_check_hostname (cert, host);
    if (val)
415
    {
416
        msg_Err (session, "Certificate does not match \"%s\"", host);
417
        val = gnutls_CertSearch (session, host, service, data);
418 419
    }
error:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
420
    gnutls_x509_crt_deinit (cert);
421
    return val;
422 423
}

424 425 426
static int
gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
{
427 428 429
    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
430

431 432
    const char *errp;
    int val = gnutls_priority_set_direct (session, priorities, &errp);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
433 434
    if (val < 0)
    {
435
        msg_Err (obj, "cannot set TLS priorities \"%s\": %s", errp,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
436
                 gnutls_strerror (val));
437
        val = VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
438
    }
439 440 441 442
    else
        val = VLC_SUCCESS;
    free (priorities);
    return val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
443 444
}

445

446
/**
447
 * TLS credentials private data
448
 */
449
struct vlc_tls_creds_sys
450
{
451
    gnutls_certificate_credentials_t x509_cred;
452
    gnutls_dh_params_t dh_params; /* XXX: used for server only */
453 454
    int (*handshake) (vlc_tls_t *, const char *, const char *);
        /* ^^ XXX: useful for server only */
455 456 457 458 459 460 461
};


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

466 467 468
    if (sys->handshaked)
        gnutls_bye (sys->session, GNUTLS_SHUT_WR);
    gnutls_deinit (sys->session);
469

470
    free (sys);
471
    (void) crd;
472 473 474
}


475 476 477
/**
 * Initializes a server-side TLS session.
 */
478
static int gnutls_SessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
479
                               int type, int fd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
480
{
481 482
    vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
    if (unlikely(sys == NULL))
483
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
484

485 486 487 488
    session->sys = sys;
    session->sock.p_sys = session;
    session->sock.pf_send = gnutls_Send;
    session->sock.pf_recv = gnutls_Recv;
489
    session->handshake = crd->sys->handshake;
490
    sys->handshaked = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
491

492
    int val = gnutls_init (&sys->session, type);
493
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
494
    {
495
        msg_Err (session, "cannot initialize TLS session: %s",
496 497
                 gnutls_strerror (val));
        free (sys);
498
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
499
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
500

501
    if (gnutls_SessionPrioritize (VLC_OBJECT (crd), sys->session))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
502
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
503

504
    val = gnutls_credentials_set (sys->session, GNUTLS_CRD_CERTIFICATE,
505
                                  crd->sys->x509_cred);
506
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
507
    {
508
        msg_Err (session, "cannot set TLS session credentials: %s",
509
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
510
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
511 512
    }

513 514
    gnutls_transport_set_ptr (sys->session,
                              (gnutls_transport_ptr_t)(intptr_t)fd);
515
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
516 517

error:
518 519
    gnutls_SessionClose (crd, session);
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
520 521
}

522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
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
556

557
/**
558
 * Adds one or more Certificate Authorities to the trusted set.
559
 *
560
 * @param path (UTF-8) path to an X.509 certificates list.
561 562
 *
 * @return -1 on error, 0 on success.
563
 */
564
static int gnutls_AddCA (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
565
{
566 567 568 569 570 571 572 573 574 575 576
    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,
    };
577

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

    /* enables peer's certificate verification */
591
    crd->sys->handshake = gnutls_HandshakeAndValidate;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
592 593 594 595
    return VLC_SUCCESS;
}


596
/**
597
 * Adds a Certificates Revocation List to be sent to TLS clients.
598
 *
599
 * @param path (UTF-8) path of the CRL file.
600 601 602
 *
 * @return -1 on error, 0 on success.
 */
603
static int gnutls_AddCRL (vlc_tls_creds_t *crd, const char *path)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
604
{
605 606 607 608 609 610 611 612 613 614 615
    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
616

617 618 619
    int val = gnutls_certificate_set_x509_crl_mem (crd->sys->x509_cred, &d,
                                                   GNUTLS_X509_FMT_PEM);
    block_Release (block);
620
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
621
    {
622
        msg_Err (crd, "cannot add CRL (%s): %s", path, gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
623 624
        return VLC_EGENERIC;
    }
625
    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
626 627
    return VLC_SUCCESS;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
628

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

630
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
631
 * Allocates a whole server's TLS credentials.
632
 */
633
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
634 635 636
{
    int val;

637
    if (gnutls_Init (VLC_OBJECT(crd)))
638 639
        return VLC_EGENERIC;

640 641
    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
642
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
643

644
    crd->sys     = sys;
645 646
    crd->add_CA  = gnutls_AddCA;
    crd->add_CRL = gnutls_AddCRL;
647
    crd->open    = gnutls_ServerSessionOpen;
648
    crd->close   = gnutls_SessionClose;
649
    /* No certificate validation by default */
650
    sys->handshake  = gnutls_ContinueHandshake;
651

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
652
    /* Sets server's credentials */
653 654
    val = gnutls_certificate_allocate_credentials (&sys->x509_cred);
    if (val != 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
655
    {
656
        msg_Err (crd, "cannot allocate credentials: %s",
657
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
658
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
659 660
    }

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
    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,
685
                                                GNUTLS_X509_FMT_PEM);
686 687
    block_Release (keyblock);
    block_Release (certblock);
688
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
689
    {
690
        msg_Err (crd, "cannot load X.509 key: %s", gnutls_strerror (val));
691
        gnutls_certificate_free_credentials (sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
692
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
693 694
    }

695
    /* FIXME:
696
     * - support other cipher suites
697
     */
698
    val = gnutls_dh_params_init (&sys->dh_params);
699 700
    if (val >= 0)
    {
701 702 703 704 705
        const gnutls_datum_t data = {
            .data = (unsigned char *)dh_params,
            .size = sizeof (dh_params) - 1,
        };

706
        val = gnutls_dh_params_import_pkcs3 (sys->dh_params, &data,
707 708
                                             GNUTLS_X509_FMT_PEM);
        if (val == 0)
709 710
            gnutls_certificate_set_dh_params (sys->x509_cred,
                                              sys->dh_params);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
711
    }
712
    if (val < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
713
    {
714
        msg_Err (crd, "cannot initialize DHE cipher suites: %s",
715
                 gnutls_strerror (val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
716 717
    }

718
    return VLC_SUCCESS;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
719 720

error:
721
    free (sys);
722
    gnutls_Deinit (VLC_OBJECT(crd));
723
    return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
724 725
}

726
/**
727
 * Destroys a TLS server object.
728
 */
729
static void CloseServer (vlc_tls_creds_t *crd)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
730
{
731
    vlc_tls_creds_sys_t *sys = crd->sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
732

733
    /* all sessions depending on the server are now deinitialized */
734 735 736
    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
737

738
    gnutls_Deinit (VLC_OBJECT(crd));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
739
}
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793

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