gnutls.c 32.3 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-2006 Rémi Denis-Courmont
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
5
 * $Id$
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
6
 *
7
 * Authors: Rémi Denis-Courmont <rem # videolan.org>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
22 23 24 25 26
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Cleanup  
Rémi Denis-Courmont committed
27

28 29 30 31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

Rémi Denis-Courmont's avatar
Cleanup  
Rémi Denis-Courmont committed
32
#include <vlc/vlc.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
33
#include <errno.h>
34
#include <time.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
35

36 37 38 39 40 41 42 43 44 45 46 47 48
#include <sys/types.h>
#include <errno.h>
#ifdef HAVE_DIRENT_H
# include <dirent.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
# ifdef HAVE_UNISTD_H
#  include <unistd.h>
# endif
#endif


Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
49
#include "vlc_tls.h"
Clément Stenac's avatar
Clément Stenac committed
50
#include <vlc_charset.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
51

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
52
#include <gcrypt.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
53
#include <gnutls/gnutls.h>
54
#include <gnutls/x509.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
55

56
#define CACHE_TIMEOUT     3600
57
#define CACHE_SIZE          64
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
58

59 60
#include "dhparams.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
61 62 63
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
64 65 66 67
static int  OpenClient  (vlc_object_t *);
static void CloseClient (vlc_object_t *);
static int  OpenServer  (vlc_object_t *);
static void CloseServer (vlc_object_t *);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
68

69 70
#define CACHE_TIMEOUT_TEXT N_("Expiration time for resumed TLS sessions")
#define CACHE_TIMEOUT_LONGTEXT N_( \
71 72
    "It is possible to cache the resumed TLS sessions. This is the expiration "\
    "time of the sessions stored in this cache, in seconds." )
73 74 75

#define CACHE_SIZE_TEXT N_("Number of resumed TLS sessions")
#define CACHE_SIZE_LONGTEXT N_( \
76
    "This is the maximum number of resumed TLS sessions that " \
77 78
    "the cache will hold." )

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
79
vlc_module_begin();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
80
    set_shortname( "GnuTLS" );
81 82 83
    set_description( _("GnuTLS transport layer security") );
    set_capability( "tls client", 1 );
    set_callbacks( OpenClient, CloseClient );
Clément Stenac's avatar
Clément Stenac committed
84 85
    set_category( CAT_ADVANCED );
    set_subcategory( SUBCAT_ADVANCED_MISC );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
86

87
    add_obsolete_bool( "tls-check-cert" );
Rémi Denis-Courmont's avatar
typo  
Rémi Denis-Courmont committed
88
    add_obsolete_bool( "tls-check-hostname" );
89

90 91 92 93 94 95 96
    add_submodule();
        set_description( _("GnuTLS server") );
        set_capability( "tls server", 1 );
        set_category( CAT_ADVANCED );
        set_subcategory( SUBCAT_ADVANCED_MISC );
        set_callbacks( OpenServer, CloseServer );

97
        add_obsolete_integer( "gnutls-dh-bits" );
98
        add_integer( "gnutls-cache-timeout", CACHE_TIMEOUT, NULL,
99
                    CACHE_TIMEOUT_TEXT, CACHE_TIMEOUT_LONGTEXT, true );
100
        add_integer( "gnutls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
101
                    CACHE_SIZE_LONGTEXT, true );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
102 103 104
vlc_module_end();


105

106 107 108 109 110 111 112 113 114
#ifdef LIBVLC_USE_PTHREAD
GCRY_THREAD_OPTION_PTHREAD_IMPL;
# define gcry_threads_vlc gcry_threads_pthread
#else
/**
 * gcrypt thread option VLC implementation
 */

static int gcry_vlc_mutex_init( void **p_sys )
115
{
116 117
    int i_val;
    vlc_mutex_t *p_lock = (vlc_mutex_t *)malloc( sizeof( vlc_mutex_t ) );
118

119 120
    if( p_lock == NULL)
        return ENOMEM;
121

122
    i_val = vlc_mutex_init( p_lock );
123 124 125 126 127 128
    if( i_val )
        free( p_lock );
    else
        *p_sys = p_lock;
    return i_val;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129

130
static int gcry_vlc_mutex_destroy( void **p_sys )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
131
{
132
    vlc_mutex_t *p_lock = (vlc_mutex_t *)*p_sys;
Rafaël Carré's avatar
Rafaël Carré committed
133
    vlc_mutex_destroy( p_lock );
134
    free( p_lock );
Rafaël Carré's avatar
Rafaël Carré committed
135
    return VLC_SUCCESS;
136
}
137

138 139
static int gcry_vlc_mutex_lock( void **p_sys )
{
Rafaël Carré's avatar
Rafaël Carré committed
140 141
    vlc_mutex_lock( (vlc_mutex_t *)*p_sys );
    return VLC_SUCCESS;
142
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
143

144 145
static int gcry_vlc_mutex_unlock( void **lock )
{
Rafaël Carré's avatar
Rafaël Carré committed
146 147
    vlc_mutex_unlock( (vlc_mutex_t *)*lock );
    return VLC_SUCCESS;
148
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
149

150
static struct gcry_thread_cbs gcry_threads_vlc =
151
{
152 153 154 155 156 157 158 159
    GCRY_THREAD_OPTION_USER,
    NULL,
    gcry_vlc_mutex_init,
    gcry_vlc_mutex_destroy,
    gcry_vlc_mutex_lock,
    gcry_vlc_mutex_unlock
};
#endif
160 161


162 163 164 165 166
/**
 * Initializes GnuTLS with proper locking.
 * @return VLC_SUCCESS on success, a VLC error code otherwise.
 */
static int gnutls_Init (vlc_object_t *p_this)
167
{
168 169
    int ret = VLC_EGENERIC;

170
    vlc_mutex_t *lock = var_AcquireMutex ("gnutls_mutex");
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200

    gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_vlc);
    if (gnutls_global_init ())
    {
        msg_Err (p_this, "cannot initialize GnuTLS");
        goto error;
    }

    const char *psz_version = gnutls_check_version ("1.3.3");
    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:
    vlc_mutex_unlock (lock);
    return ret;
}


/**
 * Deinitializes GnuTLS.
 */
static void gnutls_Deinit (vlc_object_t *p_this)
{
201
    vlc_mutex_t *lock = var_AcquireMutex( "gnutls_mutex" );
202 203 204 205 206

    gnutls_global_deinit ();
    msg_Dbg (p_this, "GnuTLS deinitialized");
    vlc_mutex_unlock (lock);
}
207 208


209 210 211 212 213
static int gnutls_Error (vlc_object_t *obj, int val)
{
    switch (val)
    {
        case GNUTLS_E_AGAIN:
214
#ifndef WIN32
215 216
            errno = EAGAIN;
            break;
217 218
#endif
            /* WinSock does not return EAGAIN, return EINTR instead */
219 220

        case GNUTLS_E_INTERRUPTED:
221 222
#ifdef WIN32
            WSASetLastError (WSAEINTR);
223
#else
224
            errno = EINTR;
225
#endif
226 227 228 229
            break;

        default:
            msg_Err (obj, "%s", gnutls_strerror (val));
230
#ifndef NDEBUG
231 232 233
            if (!gnutls_error_is_fatal (val))
                msg_Err (obj, "Error above should be handled");
#endif
234 235
#ifdef WIN32
            WSASetLastError (WSAECONNRESET);
236
#else
237
            errno = ECONNRESET;
238
#endif
239 240 241 242 243
    }
    return -1;
}


244 245
struct tls_session_sys_t
{
246 247
    gnutls_session_t session;
    char            *psz_hostname;
248
    bool       b_handshaked;
249 250 251
};


252
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
253
 * Sends data through a TLS session.
254
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
255
static int
256
gnutls_Send( void *p_session, const void *buf, int i_length )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
257 258
{
    int val;
259
    tls_session_sys_t *p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
260

261 262 263
    p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);

    val = gnutls_record_send( p_sys->session, buf, i_length );
264
    return (val < 0) ? gnutls_Error ((vlc_object_t *)p_session, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
265 266 267
}


268
/**
269
 * Receives data through a TLS session.
270
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
271
static int
272
gnutls_Recv( void *p_session, void *buf, int i_length )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
273 274
{
    int val;
275 276 277
    tls_session_sys_t *p_sys;

    p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
278

279
    val = gnutls_record_recv( p_sys->session, buf, i_length );
280
    return (val < 0) ? gnutls_Error ((vlc_object_t *)p_session, val) : val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
281 282 283
}


284
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
285 286 287 288 289
 * Starts or continues the TLS handshake.
 *
 * @return -1 on fatal error, 0 on succesful handshake completion,
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
290
 */
291
static int
292
gnutls_ContinueHandshake (tls_session_t *p_session)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
293
{
294
    tls_session_sys_t *p_sys = p_session->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
295 296
    int val;

297 298 299
#ifdef WIN32
    WSASetLastError( 0 );
#endif
300 301 302
    val = gnutls_handshake( p_sys->session );
    if( ( val == GNUTLS_E_AGAIN ) || ( val == GNUTLS_E_INTERRUPTED ) )
        return 1 + gnutls_record_get_direction( p_sys->session );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
303 304 305

    if( val < 0 )
    {
306 307 308
#ifdef WIN32
        msg_Dbg( p_session, "Winsock error %d", WSAGetLastError( ) );
#endif
309
        msg_Err( p_session, "TLS handshake error: %s",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
310
                 gnutls_strerror( val ) );
311
        return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
312
    }
313

314
    p_sys->b_handshaked = true;
315 316 317
    return 0;
}

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

typedef struct
{
    int flag;
    const char *msg;
} error_msg_t;

static const error_msg_t cert_errors[] =
{
    { GNUTLS_CERT_INVALID,
        "Certificate could not be verified" },
    { GNUTLS_CERT_REVOKED,
        "Certificate was revoked" },
    { GNUTLS_CERT_SIGNER_NOT_FOUND,
        "Certificate's signer was not found" },
    { GNUTLS_CERT_SIGNER_NOT_CA,
        "Certificate's signer is not a CA" },
    { GNUTLS_CERT_INSECURE_ALGORITHM,
        "Insecure certificate signature algorithm" },
    { 0, NULL }
};


341
static int
342
gnutls_HandshakeAndValidate( tls_session_t *session )
343
{
344 345 346
    int val = gnutls_ContinueHandshake( session );
    if( val )
        return val;
347

348
    tls_session_sys_t *p_sys = (tls_session_sys_t *)(session->p_sys);
349

350 351 352 353 354 355 356 357
    /* certificates chain verification */
    unsigned status;
    val = gnutls_certificate_verify_peers2( p_sys->session, &status );

    if( val )
    {
        msg_Err( session, "Certificate verification failed: %s",
                 gnutls_strerror( val ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
358
        return -1;
359
    }
360

361 362 363 364
    if( status )
    {
        msg_Err( session, "TLS session: access denied" );
        for( const error_msg_t *e = cert_errors; e->flag; e++ )
365
        {
366 367
            if( status & e->flag )
            {
368
                msg_Err( session, "%s", e->msg );
369 370
                status &= ~e->flag;
            }
371 372 373
        }

        if( status )
374 375 376
            msg_Err( session,
                     "unknown certificate error (you found a bug in VLC)" );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
377
        return -1;
378 379 380
    }

    /* certificate (host)name verification */
381 382
    const gnutls_datum_t *data;
    data = gnutls_certificate_get_peers (p_sys->session, &(unsigned){0});
383 384 385
    if( data == NULL )
    {
        msg_Err( session, "Peer certificate not available" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
386
        return -1;
387 388
    }

389
    gnutls_x509_crt_t cert;
390 391 392 393
    val = gnutls_x509_crt_init( &cert );
    if( val )
    {
        msg_Err( session, "x509 fatal error: %s", gnutls_strerror( val ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
394
        return -1;
395 396 397 398 399 400 401
    }

    val = gnutls_x509_crt_import( cert, data, GNUTLS_X509_FMT_DER );
    if( val )
    {
        msg_Err( session, "Certificate import error: %s",
                 gnutls_strerror( val ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
402
        goto error;
403
    }
404

405 406
    assert( p_sys->psz_hostname != NULL );
    if ( !gnutls_x509_crt_check_hostname( cert, p_sys->psz_hostname ) )
407
    {
408 409 410
        msg_Err( session, "Certificate does not match \"%s\"",
                 p_sys->psz_hostname );
        goto error;
411 412 413 414 415
    }

    if( gnutls_x509_crt_get_expiration_time( cert ) < time( NULL ) )
    {
        msg_Err( session, "Certificate expired" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
416
        goto error;
417 418
    }

419 420 421
    if( gnutls_x509_crt_get_activation_time( cert ) > time ( NULL ) )
    {
        msg_Err( session, "Certificate not yet valid" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
422
        goto error;
423 424 425 426 427 428 429
    }

    gnutls_x509_crt_deinit( cert );
    msg_Dbg( session, "TLS/x509 certificate verified" );
    return 0;

error:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
430
    gnutls_x509_crt_deinit( cert );
431
    return -1;
432 433
}

434
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
435
 * Sets the operating system file descriptor backend for the TLS sesison.
436 437 438
 *
 * @param fd stream socket already connected with the peer.
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
439 440
static void
gnutls_SetFD (tls_session_t *p_session, int fd)
441
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
442
    gnutls_transport_set_ptr (p_session->p_sys->session,
443
                              (gnutls_transport_ptr_t)(intptr_t)fd);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
444 445
}

446 447
typedef int (*tls_prio_func) (gnutls_session_t, const int *);

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
448
static int
449 450 451
gnutls_SetPriority (vlc_object_t *restrict obj, const char *restrict name,
                    tls_prio_func func, gnutls_session_t session,
                    const int *restrict values)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
452
{
453
    int val = func (session, values);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
454 455
    if (val < 0)
    {
456
        msg_Err (obj, "cannot set %s priorities: %s", name,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
457 458 459
                 gnutls_strerror (val));
        return VLC_EGENERIC;
    }
460 461
    return VLC_SUCCESS;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
462

463 464 465 466

static int
gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
{
467
    /* Note that ordering matters (on the client side) */
468
    static const int protos[] =
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
469
    {
470 471 472 473 474 475 476
        GNUTLS_TLS1_1,
        GNUTLS_TLS1_0,
        GNUTLS_SSL3,
        0
    };
    static const int comps[] =
    {
477
        GNUTLS_COMP_DEFLATE,
478 479 480
        GNUTLS_COMP_NULL,
        0
    };
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
    static const int macs[] =
    {
        GNUTLS_MAC_SHA1,
        GNUTLS_MAC_RMD160, // RIPEMD
        GNUTLS_MAC_MD5,
        //GNUTLS_MAC_MD2,
        //GNUTLS_MAC_NULL,
        0
    };
    static const int ciphers[] =
    {
        GNUTLS_CIPHER_AES_256_CBC,
        GNUTLS_CIPHER_AES_128_CBC,
        GNUTLS_CIPHER_3DES_CBC,
        GNUTLS_CIPHER_ARCFOUR_128,
        //GNUTLS_CIPHER_DES_CBC,
        //GNUTLS_CIPHER_ARCFOUR_40,
        //GNUTLS_CIPHER_RC2_40_CBC,
        //GNUTLS_CIPHER_NULL,
        0
    };
502 503 504 505 506 507 508 509 510 511 512 513 514 515
    static const int kx[] =
    {
        GNUTLS_KX_DHE_RSA,
        GNUTLS_KX_DHE_DSS,
        GNUTLS_KX_RSA,
        //GNUTLS_KX_RSA_EXPORT,
        //GNUTLS_KX_DHE_PSK, TODO
        //GNUTLS_KX_PSK,     TODO
        //GNUTLS_KX_SRP_RSA, TODO
        //GNUTLS_KX_SRP_DSS, TODO
        //GNUTLS_KX_SRP,     TODO
        //GNUTLS_KX_ANON_DH,
        0
    };
516 517 518 519 520 521
    static const int cert_types[] =
    {
        GNUTLS_CRT_X509,
        //GNUTLS_CRT_OPENPGP, TODO
        0
    };
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
522

523
    int val = gnutls_set_default_priority (session);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
524 525
    if (val < 0)
    {
526
        msg_Err (obj, "cannot set default TLS priorities: %s",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
527 528 529 530
                 gnutls_strerror (val));
        return VLC_EGENERIC;
    }

531 532
    if (gnutls_SetPriority (obj, "protocols",
                            gnutls_protocol_set_priority, session, protos)
533
     || gnutls_SetPriority (obj, "compression algorithms",
534
                            gnutls_compression_set_priority, session, comps)
535
     || gnutls_SetPriority (obj, "MAC algorithms",
536 537 538
                            gnutls_mac_set_priority, session, macs)
     || gnutls_SetPriority (obj, "ciphers",
                            gnutls_cipher_set_priority, session, ciphers)
539 540
     || gnutls_SetPriority (obj, "key exchange algorithms",
                            gnutls_kx_set_priority, session, kx)
541 542 543 544 545
     || gnutls_SetPriority (obj, "certificate types",
                            gnutls_certificate_type_set_priority, session,
                            cert_types))
        return VLC_EGENERIC;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
546 547 548 549
    return VLC_SUCCESS;
}


550 551
static int
gnutls_Addx509File( vlc_object_t *p_this,
552
                    gnutls_certificate_credentials_t cred,
553
                    const char *psz_path, bool b_priv );
554

555
static int
556
gnutls_Addx509Directory( vlc_object_t *p_this,
557
                         gnutls_certificate_credentials_t cred,
558
                         const char *psz_dirname,
559
                         bool b_priv )
560 561 562 563 564 565
{
    DIR* dir;

    if( *psz_dirname == '\0' )
        psz_dirname = ".";

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
566
    dir = utf8_opendir( psz_dirname );
567 568
    if( dir == NULL )
    {
569 570 571 572 573 574 575 576
        if (errno != ENOENT)
        {
            msg_Err (p_this, "cannot open directory (%s): %m", psz_dirname);
            return VLC_EGENERIC;
        }

        msg_Dbg (p_this, "creating empty certificate directory: %s",
                 psz_dirname);
577
        utf8_mkdir (psz_dirname, b_priv ? 0700 : 0755);
578
        return VLC_SUCCESS;
579
    }
580 581 582 583 584 585 586 587 588 589 590
#ifdef S_ISLNK
    else
    {
        struct stat st1, st2;
        int fd = dirfd( dir );

        /*
         * Gets stats for the directory path, checks that it is not a
         * symbolic link (to avoid possibly infinite recursion), and verifies
         * that the inode is still the same, to avoid TOCTOU race condition.
         */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
591 592
        if( ( fd == -1)
         || fstat( fd, &st1 ) || utf8_lstat( psz_dirname, &st2 )
593 594
         || S_ISLNK( st2.st_mode ) || ( st1.st_ino != st2.st_ino ) )
        {
595
            closedir( dir );
596 597 598 599
            return VLC_EGENERIC;
        }
    }
#endif
600

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
601
    for (;;)
602
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
603 604 605
        char *ent = utf8_readdir (dir);
        if (ent == NULL)
            break;
606

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
607
        if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
608 609
            continue;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
610 611 612
        char path[strlen (psz_dirname) + strlen (ent) + 2];
        sprintf (path, "%s"DIR_SEP"%s", psz_dirname, ent);
        free (ent);
613

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
614
        gnutls_Addx509File( p_this, cred, path, b_priv );
615 616
    }

617
    closedir( dir );
618 619 620 621 622 623 624
    return VLC_SUCCESS;
}


static int
gnutls_Addx509File( vlc_object_t *p_this,
                    gnutls_certificate_credentials cred,
625
                    const char *psz_path, bool b_priv )
626 627 628 629 630 631
{
    struct stat st;

    if( utf8_stat( psz_path, &st ) == 0 )
    {
        if( S_ISREG( st.st_mode ) )
632
        {
633 634
            char *psz_localname = ToLocale( psz_path );
            int i = b_priv
635
                    ? gnutls_certificate_set_x509_key_file( cred,
636 637 638 639 640 641 642
                    psz_localname,  psz_localname, GNUTLS_X509_FMT_PEM )
                : gnutls_certificate_set_x509_trust_file( cred,
                        psz_localname, GNUTLS_X509_FMT_PEM );
            LocaleFree( psz_localname );

            if( i < 0 )
            {
643
                msg_Warn( p_this, "cannot add x509 credentials (%s): %s",
644 645
                          psz_path, gnutls_strerror( i ) );
                return VLC_EGENERIC;
646
            }
647
            else
648
            {
649
                msg_Dbg( p_this, "added x509 credentials (%s)",
650 651
                         psz_path );
                return VLC_SUCCESS;
652 653
            }
        }
654 655 656
        else if( S_ISDIR( st.st_mode ) )
        {
            msg_Dbg( p_this,
657
                     "looking recursively for x509 credentials in %s",
658 659 660
                     psz_path );
            return gnutls_Addx509Directory( p_this, cred, psz_path, b_priv);
        }
661
    }
662
    else
663
        msg_Warn( p_this, "cannot add x509 credentials (%s): %m", psz_path );
664
    return VLC_EGENERIC;
665 666
}

667

668 669 670
/** TLS client session data */
typedef struct tls_client_sys_t
{
671 672
    struct tls_session_sys_t         session;
    gnutls_certificate_credentials_t x509_cred;
673 674 675
} tls_client_sys_t;


676 677 678
/**
 * Initializes a client-side TLS session.
 */
679
static int OpenClient (vlc_object_t *obj)
680
{
681
    tls_session_t *p_session = (tls_session_t *)obj;
682 683
    int i_val;

684 685
    if (gnutls_Init (obj))
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
686

687 688
    tls_client_sys_t *p_sys = malloc (sizeof (*p_sys));
    if (p_sys == NULL)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
689
    {
690 691
        gnutls_Deinit (obj);
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
692 693
    }

694
    p_session->p_sys = &p_sys->session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
695 696 697
    p_session->sock.p_sys = p_session;
    p_session->sock.pf_send = gnutls_Send;
    p_session->sock.pf_recv = gnutls_Recv;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
698
    p_session->pf_set_fd = gnutls_SetFD;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
699

700
    p_sys->session.b_handshaked = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
701

702
    const char *homedir = obj->p_libvlc->psz_datadir,
703
               *datadir = config_GetDataDir ();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
704
    size_t l1 = strlen (homedir), l2 = strlen (datadir);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
705 706
    char path[((l1 > l2) ? l1 : l2) + sizeof ("/ca-certificates.crt")];
    //                              > sizeof ("/ssl/private")
707
    //                              > sizeof ("/ssl/certs")
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
708

709 710
    i_val = gnutls_certificate_allocate_credentials (&p_sys->x509_cred);
    if (i_val != 0)
711
    {
712 713
        msg_Err (obj, "cannot allocate X509 credentials: %s",
                 gnutls_strerror (i_val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
714
        goto error;
715 716
    }

717
    sprintf (path, "%s/ssl", homedir);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
718
    utf8_mkdir (path, 0755);
719

720 721
    sprintf (path, "%s/ssl/certs", homedir);
    gnutls_Addx509Directory (VLC_OBJECT (p_session),
722
                             p_sys->x509_cred, path, false);
723 724 725

    sprintf (path, "%s/ca-certificates.crt", datadir);
    gnutls_Addx509File (VLC_OBJECT (p_session),
726
                        p_sys->x509_cred, path, false);
727 728
    p_session->pf_handshake = gnutls_HandshakeAndValidate;
    /*p_session->pf_handshake = gnutls_ContinueHandshake;*/
729

730
    sprintf (path, "%s/ssl/private", homedir);
731
    gnutls_Addx509Directory (VLC_OBJECT (p_session), p_sys->x509_cred,
732
                             path, true);
733

734
    i_val = gnutls_init (&p_sys->session.session, GNUTLS_CLIENT);
735
    if (i_val != 0)
736
    {
737 738 739
        msg_Err (obj, "cannot initialize TLS session: %s",
                 gnutls_strerror (i_val));
        gnutls_certificate_free_credentials (p_sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
740
        goto error;
741 742
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
743 744 745
    if (gnutls_SessionPrioritize (VLC_OBJECT (p_session),
                                  p_sys->session.session))
        goto s_error;
746

747 748 749
    /* minimum DH prime bits */
    gnutls_dh_set_prime_bits (p_sys->session.session, 1024);

750
    i_val = gnutls_credentials_set (p_sys->session.session,
751
                                    GNUTLS_CRD_CERTIFICATE,
752 753
                                    p_sys->x509_cred);
    if (i_val < 0)
754
    {
755 756
        msg_Err (obj, "cannot set TLS session credentials: %s",
                 gnutls_strerror (i_val));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
757
        goto s_error;
758 759
    }

760
    char *servername = var_GetNonEmptyString (p_session, "tls-server-name");
761 762 763 764 765 766
    if (servername == NULL )
        msg_Err (p_session, "server name missing for TLS session");

    p_sys->session.psz_hostname = servername;
    gnutls_server_name_set (p_sys->session.session, GNUTLS_NAME_DNS,
                            servername, strlen (servername));
767

768
    return VLC_SUCCESS;
769

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
770
s_error:
771 772
    gnutls_deinit (p_sys->session.session);
    gnutls_certificate_free_credentials (p_sys->x509_cred);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
773
error:
774 775 776 777
    gnutls_Deinit (obj);
    free (p_sys);
    return VLC_EGENERIC;
}
778

779 780 781 782 783 784

static void CloseClient (vlc_object_t *obj)
{
    tls_session_t *client = (tls_session_t *)obj;
    tls_client_sys_t *p_sys = (tls_client_sys_t *)(client->p_sys);

785
    if (p_sys->session.b_handshaked == true)
786 787 788 789 790 791 792 793
        gnutls_bye (p_sys->session.session, GNUTLS_SHUT_WR);
    gnutls_deinit (p_sys->session.session);
    /* credentials must be free'd *after* gnutls_deinit() */
    gnutls_certificate_free_credentials (p_sys->x509_cred);

    gnutls_Deinit (obj);
    free (p_sys->session.psz_hostname);
    free (p_sys);
794 795 796
}


797 798 799 800 801
/**
 * Server-side TLS
 */
struct tls_server_sys_t
{
802 803
    gnutls_certificate_credentials_t x509_cred;
    gnutls_dh_params_t               dh_params;
804 805 806

    struct saved_session_t          *p_cache;
    struct saved_session_t          *p_store;
807 808
    int                              i_cache_size;
    vlc_mutex_t                      cache_lock;
809

810
    int                            (*pf_handshake) (tls_session_t *);
811 812 813
};


814 815 816
/**
 * TLS session resumption callbacks (server-side)
 */
817 818 819 820 821 822 823 824 825 826 827 828 829
#define MAX_SESSION_ID    32
#define MAX_SESSION_DATA  1024

typedef struct saved_session_t
{
    char id[MAX_SESSION_ID];
    char data[MAX_SESSION_DATA];

    unsigned i_idlen;
    unsigned i_datalen;
} saved_session_t;


830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
static int cb_store( void *p_server, gnutls_datum key, gnutls_datum data )
{
    tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;

    if( ( p_sys->i_cache_size == 0 )
     || ( key.size > MAX_SESSION_ID )
     || ( data.size > MAX_SESSION_DATA ) )
        return -1;

    vlc_mutex_lock( &p_sys->cache_lock );

    memcpy( p_sys->p_store->id, key.data, key.size);
    memcpy( p_sys->p_store->data, data.data, data.size );
    p_sys->p_store->i_idlen = key.size;
    p_sys->p_store->i_datalen = data.size;

    p_sys->p_store++;
    if( ( p_sys->p_store - p_sys->p_cache ) == p_sys->i_cache_size )
        p_sys->p_store = p_sys->p_cache;

    vlc_mutex_unlock( &p_sys->cache_lock );

    return 0;
}


static gnutls_datum cb_fetch( void *p_server, gnutls_datum key )
{
858
    static const gnutls_datum_t err_datum = { NULL, 0 };
859 860 861 862 863 864 865 866 867 868 869 870 871
    tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
    saved_session_t *p_session, *p_end;

    p_session = p_sys->p_cache;
    p_end = p_session + p_sys->i_cache_size;

    vlc_mutex_lock( &p_sys->cache_lock );

    while( p_session < p_end )
    {
        if( ( p_session->i_idlen == key.size )
         && !memcmp( p_session->id, key.data, key.size ) )
        {
872
            gnutls_datum_t data;
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923

            data.size = p_session->i_datalen;

            data.data = gnutls_malloc( data.size );
            if( data.data == NULL )
            {
                vlc_mutex_unlock( &p_sys->cache_lock );
                return err_datum;
            }

            memcpy( data.data, p_session->data, data.size );
            vlc_mutex_unlock( &p_sys->cache_lock );
            return data;
        }
        p_session++;
    }

    vlc_mutex_unlock( &p_sys->cache_lock );

    return err_datum;
}


static int cb_delete( void *p_server, gnutls_datum key )
{
    tls_server_sys_t *p_sys = ((tls_server_t *)p_server)->p_sys;
    saved_session_t *p_session, *p_end;

    p_session = p_sys->p_cache;
    p_end = p_session + p_sys->i_cache_size;

    vlc_mutex_lock( &p_sys->cache_lock );

    while( p_session < p_end )
    {
        if( ( p_session->i_idlen == key.size )
         && !memcmp( p_session->id, key.data, key.size ) )
        {
            p_session->i_datalen = p_session->i_idlen = 0;
            vlc_mutex_unlock( &p_sys->cache_lock );
            return 0;
        }
        p_session++;
    }

    vlc_mutex_unlock( &p_sys->cache_lock );

    return -1;
}


924 925 926 927 928
/**
 * Terminates TLS session and releases session data.
 * You still have to close the socket yourself.
 */
static void
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
929
gnutls_SessionClose (tls_server_t *p_server, tls_session_t *p_session)
930 931
{
    tls_session_sys_t *p_sys = p_session->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
932
    (void)p_server;
933

934
    if( p_sys->b_handshaked == true )
935 936 937 938
        gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
    gnutls_deinit( p_sys->session );

    vlc_object_detach( p_session );
939
    vlc_object_release( p_session );
940 941 942 943 944

    free( p_sys );
}


945 946 947
/**
 * Initializes a server-side TLS session.
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
948 949 950 951
static tls_session_t *
gnutls_ServerSessionPrepare( tls_server_t *p_server )
{
    tls_session_t *p_session;
952
    tls_server_sys_t *p_server_sys;
953
    gnutls_session_t session;
954
    int i_val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
955

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
956 957 958
    p_session = vlc_object_create( p_server, sizeof (struct tls_session_t) );
    if( p_session == NULL )
        return NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
959

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
960 961 962
    p_session->p_sys = malloc( sizeof(struct tls_session_sys_t) );
    if( p_session->p_sys == NULL )
    {
963
        vlc_object_release( p_session );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
964 965 966
        return NULL;
    }