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
dionoea's avatar
dionoea 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

#include <vlc/vlc.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
29
#include <errno.h>
30
#include <time.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
31

32
33
34
35
36
37
38
39
40
41
42
43
44
#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
45
#include "vlc_tls.h"
zorglub's avatar
zorglub committed
46
#include <vlc_charset.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
47

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
48
#include <gcrypt.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
49
#include <gnutls/gnutls.h>
50
#include <gnutls/x509.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
51

52
#define CACHE_TIMEOUT     3600
53
#define CACHE_SIZE          64
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
54

55
56
#include "dhparams.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
57
58
59
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
60
61
62
63
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
64

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

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
75
vlc_module_begin();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
76
    set_shortname( "GnuTLS" );
77
78
79
    set_description( _("GnuTLS transport layer security") );
    set_capability( "tls client", 1 );
    set_callbacks( OpenClient, CloseClient );
zorglub's avatar
zorglub committed
80
81
    set_category( CAT_ADVANCED );
    set_subcategory( SUBCAT_ADVANCED_MISC );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
82

83
    add_obsolete_bool( "tls-check-cert" );
Rémi Denis-Courmont's avatar
typo    
Rémi Denis-Courmont committed
84
    add_obsolete_bool( "tls-check-hostname" );
85

86
87
88
89
90
91
92
    add_submodule();
        set_description( _("GnuTLS server") );
        set_capability( "tls server", 1 );
        set_category( CAT_ADVANCED );
        set_subcategory( SUBCAT_ADVANCED_MISC );
        set_callbacks( OpenServer, CloseServer );

93
        add_obsolete_integer( "gnutls-dh-bits" );
94
95
96
97
        add_integer( "gnutls-cache-timeout", CACHE_TIMEOUT, NULL,
                    CACHE_TIMEOUT_TEXT, CACHE_TIMEOUT_LONGTEXT, VLC_TRUE );
        add_integer( "gnutls-cache-size", CACHE_SIZE, NULL, CACHE_SIZE_TEXT,
                    CACHE_SIZE_LONGTEXT, VLC_TRUE );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
98
99
100
vlc_module_end();


101

102
103
104
105
106
107
108
109
110
#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 )
111
{
112
113
    int i_val;
    vlc_mutex_t *p_lock = (vlc_mutex_t *)malloc( sizeof( vlc_mutex_t ) );
114

115
116
    if( p_lock == NULL)
        return ENOMEM;
117

118
    i_val = vlc_mutex_init( (vlc_object_t *)NULL, p_lock );
119
120
121
122
123
124
    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
125

126
static int gcry_vlc_mutex_destroy( void **p_sys )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
127
{
128
129
    int i_val;
    vlc_mutex_t *p_lock = (vlc_mutex_t *)*p_sys;
130

131
132
133
134
    i_val = vlc_mutex_destroy( p_lock );
    free( p_lock );
    return i_val;
}
135

136
137
138
139
static int gcry_vlc_mutex_lock( void **p_sys )
{
    return vlc_mutex_lock( (vlc_mutex_t *)*p_sys );
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
140

141
142
143
144
static int gcry_vlc_mutex_unlock( void **lock )
{
    return vlc_mutex_unlock( (vlc_mutex_t *)*lock );
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
145

146
static struct gcry_thread_cbs gcry_threads_vlc =
147
{
148
149
150
151
152
153
154
155
    GCRY_THREAD_OPTION_USER,
    NULL,
    gcry_vlc_mutex_init,
    gcry_vlc_mutex_destroy,
    gcry_vlc_mutex_lock,
    gcry_vlc_mutex_unlock
};
#endif
156
157


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

166
    vlc_mutex_t *lock = var_AcquireMutex ("gnutls_mutex");
167
168
169
170
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

    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)
{
197
    vlc_mutex_t *lock = var_AcquireMutex( "gnutls_mutex" );
198
199
200
201
202

    gnutls_global_deinit ();
    msg_Dbg (p_this, "GnuTLS deinitialized");
    vlc_mutex_unlock (lock);
}
203
204


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

        case GNUTLS_E_INTERRUPTED:
217
218
#ifdef WIN32
            WSASetLastError (WSAEINTR);
219
#else
220
            errno = EINTR;
221
#endif
222
223
224
225
            break;

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


240
241
struct tls_session_sys_t
{
242
243
244
    gnutls_session_t session;
    char            *psz_hostname;
    vlc_bool_t       b_handshaked;
245
246
247
};


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

257
258
259
    p_sys = (tls_session_sys_t *)(((tls_session_t *)p_session)->p_sys);

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


264
/**
265
 * Receives data through a TLS session.
266
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
267
static int
268
gnutls_Recv( void *p_session, void *buf, int i_length )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
269
270
{
    int val;
271
272
273
    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
274

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


280
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
281
282
283
284
285
 * 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.
286
 */
287
static int
288
gnutls_ContinueHandshake (tls_session_t *p_session)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
289
{
290
    tls_session_sys_t *p_sys = p_session->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
291
292
    int val;

293
294
295
#ifdef WIN32
    WSASetLastError( 0 );
#endif
296
297
298
    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
299
300
301

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
310
    p_sys->b_handshaked = VLC_TRUE;
311
312
313
    return 0;
}

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336

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


337
static int
338
gnutls_HandshakeAndValidate( tls_session_t *session )
339
{
340
341
342
    int val = gnutls_ContinueHandshake( session );
    if( val )
        return val;
343

344
    tls_session_sys_t *p_sys = (tls_session_sys_t *)(session->p_sys);
345

346
347
348
349
350
351
352
353
    /* 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
354
        return -1;
355
    }
356

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

        if( status )
370
371
372
            msg_Err( session,
                     "unknown certificate error (you found a bug in VLC)" );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
373
        return -1;
374
375
376
    }

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

385
    gnutls_x509_crt_t cert;
386
387
388
389
    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
390
        return -1;
391
392
393
394
395
396
397
    }

    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
398
        goto error;
399
    }
400

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

    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
412
        goto error;
413
414
    }

415
416
417
    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
418
        goto error;
419
420
421
422
423
424
425
    }

    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
426
    gnutls_x509_crt_deinit( cert );
427
    return -1;
428
429
}

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

442
443
typedef int (*tls_prio_func) (gnutls_session_t, const int *);

444
static int
445
446
447
gnutls_SetPriority (vlc_object_t *restrict obj, const char *restrict name,
                    tls_prio_func func, gnutls_session_t session,
                    const int *restrict values)
448
{
449
    int val = func (session, values);
450
451
    if (val < 0)
    {
452
        msg_Err (obj, "cannot set %s priorities: %s", name,
453
454
455
                 gnutls_strerror (val));
        return VLC_EGENERIC;
    }
456
457
    return VLC_SUCCESS;
}
458

459
460
461
462

static int
gnutls_SessionPrioritize (vlc_object_t *obj, gnutls_session_t session)
{
463
    /* Note that ordering matters (on the client side) */
464
    static const int protos[] =
465
    {
466
467
468
469
470
471
472
        GNUTLS_TLS1_1,
        GNUTLS_TLS1_0,
        GNUTLS_SSL3,
        0
    };
    static const int comps[] =
    {
473
        GNUTLS_COMP_DEFLATE,
474
475
476
        GNUTLS_COMP_NULL,
        0
    };
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
    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
    };
498
499
500
501
502
503
504
505
506
507
508
509
510
511
    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
    };
512
513
514
515
516
517
    static const int cert_types[] =
    {
        GNUTLS_CRT_X509,
        //GNUTLS_CRT_OPENPGP, TODO
        0
    };
518

519
    int val = gnutls_set_default_priority (session);
520
521
    if (val < 0)
    {
522
        msg_Err (obj, "cannot set default TLS priorities: %s",
523
524
525
526
                 gnutls_strerror (val));
        return VLC_EGENERIC;
    }

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

542
543
544
545
    return VLC_SUCCESS;
}


546
547
static int
gnutls_Addx509File( vlc_object_t *p_this,
548
                    gnutls_certificate_credentials_t cred,
549
550
                    const char *psz_path, vlc_bool_t b_priv );

551
static int
552
gnutls_Addx509Directory( vlc_object_t *p_this,
553
                         gnutls_certificate_credentials_t cred,
554
                         const char *psz_dirname,
555
                         vlc_bool_t b_priv )
556
557
558
559
560
561
{
    DIR* dir;

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
562
    dir = utf8_opendir( psz_dirname );
563
564
    if( dir == NULL )
    {
565
566
567
568
569
570
571
572
        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);
573
        utf8_mkdir (psz_dirname, b_priv ? 0700 : 0755);
574
        return VLC_SUCCESS;
575
    }
576
577
578
579
580
581
582
583
584
585
586
#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
587
588
        if( ( fd == -1)
         || fstat( fd, &st1 ) || utf8_lstat( psz_dirname, &st2 )
589
590
         || S_ISLNK( st2.st_mode ) || ( st1.st_ino != st2.st_ino ) )
        {
591
            closedir( dir );
592
593
594
595
            return VLC_EGENERIC;
        }
    }
#endif
596

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
597
    for (;;)
598
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
599
600
601
        char *ent = utf8_readdir (dir);
        if (ent == NULL)
            break;
602

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
603
        if ((strcmp (ent, ".") == 0) || (strcmp (ent, "..") == 0))
604
605
            continue;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
606
607
608
        char path[strlen (psz_dirname) + strlen (ent) + 2];
        sprintf (path, "%s"DIR_SEP"%s", psz_dirname, ent);
        free (ent);
609

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
610
        gnutls_Addx509File( p_this, cred, path, b_priv );
611
612
    }

613
    closedir( dir );
614
615
616
617
618
619
620
621
622
623
624
625
626
627
    return VLC_SUCCESS;
}


static int
gnutls_Addx509File( vlc_object_t *p_this,
                    gnutls_certificate_credentials cred,
                    const char *psz_path, vlc_bool_t b_priv )
{
    struct stat st;

    if( utf8_stat( psz_path, &st ) == 0 )
    {
        if( S_ISREG( st.st_mode ) )
628
        {
629
630
            char *psz_localname = ToLocale( psz_path );
            int i = b_priv
631
                    ? gnutls_certificate_set_x509_key_file( cred,
632
633
634
635
636
637
638
                    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 )
            {
639
                msg_Warn( p_this, "cannot add x509 credentials (%s): %s",
640
641
                          psz_path, gnutls_strerror( i ) );
                return VLC_EGENERIC;
642
            }
643
            else
644
            {
645
                msg_Dbg( p_this, "added x509 credentials (%s)",
646
647
                         psz_path );
                return VLC_SUCCESS;
648
649
            }
        }
650
651
652
        else if( S_ISDIR( st.st_mode ) )
        {
            msg_Dbg( p_this,
653
                     "looking recursively for x509 credentials in %s",
654
655
656
                     psz_path );
            return gnutls_Addx509Directory( p_this, cred, psz_path, b_priv);
        }
657
    }
658
    else
659
        msg_Warn( p_this, "cannot add x509 credentials (%s): %m", psz_path );
660
    return VLC_EGENERIC;
661
662
}

663

664
665
666
/** TLS client session data */
typedef struct tls_client_sys_t
{
667
668
    struct tls_session_sys_t         session;
    gnutls_certificate_credentials_t x509_cred;
669
670
671
} tls_client_sys_t;


672
673
674
/**
 * Initializes a client-side TLS session.
 */
675
static int OpenClient (vlc_object_t *obj)
676
{
677
    tls_session_t *p_session = (tls_session_t *)obj;
678
679
    int i_val;

680
681
    if (gnutls_Init (obj))
        return VLC_EGENERIC;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
682

683
684
    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
685
    {
686
687
        gnutls_Deinit (obj);
        return VLC_ENOMEM;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
688
689
    }

690
    p_session->p_sys = &p_sys->session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
691
692
693
    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
694
    p_session->pf_set_fd = gnutls_SetFD;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
695
696
697

    p_sys->session.b_handshaked = VLC_FALSE;

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

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

713
    sprintf (path, "%s/ssl", homedir);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
714
    utf8_mkdir (path, 0755);
715

716
717
718
719
720
721
722
723
724
    sprintf (path, "%s/ssl/certs", homedir);
    gnutls_Addx509Directory (VLC_OBJECT (p_session),
                             p_sys->x509_cred, path, VLC_FALSE);

    sprintf (path, "%s/ca-certificates.crt", datadir);
    gnutls_Addx509File (VLC_OBJECT (p_session),
                        p_sys->x509_cred, path, VLC_FALSE);
    p_session->pf_handshake = gnutls_HandshakeAndValidate;
    /*p_session->pf_handshake = gnutls_ContinueHandshake;*/
725

726
    sprintf (path, "%s/ssl/private", homedir);
727
    gnutls_Addx509Directory (VLC_OBJECT (p_session), p_sys->x509_cred,
728
                             path, VLC_TRUE);
729

730
    i_val = gnutls_init (&p_sys->session.session, GNUTLS_CLIENT);
731
    if (i_val != 0)
732
    {
733
734
735
        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
736
        goto error;
737
738
    }

739
740
741
    if (gnutls_SessionPrioritize (VLC_OBJECT (p_session),
                                  p_sys->session.session))
        goto s_error;
742

743
744
745
    /* minimum DH prime bits */
    gnutls_dh_set_prime_bits (p_sys->session.session, 1024);

746
    i_val = gnutls_credentials_set (p_sys->session.session,
747
                                    GNUTLS_CRD_CERTIFICATE,
748
749
                                    p_sys->x509_cred);
    if (i_val < 0)
750
    {
751
752
        msg_Err (obj, "cannot set TLS session credentials: %s",
                 gnutls_strerror (i_val));
753
        goto s_error;
754
755
    }

756
    char *servername = var_GetNonEmptyString (p_session, "tls-server-name");
757
758
759
760
761
762
    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));
763

764
    return VLC_SUCCESS;
765

766
s_error:
767
768
    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
769
error:
770
771
772
773
    gnutls_Deinit (obj);
    free (p_sys);
    return VLC_EGENERIC;
}
774

775
776
777
778
779
780
781
782
783
784
785
786
787
788
789

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

    if (p_sys->session.b_handshaked == VLC_TRUE)
        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);
790
791
792
}


793
794
795
796
797
/**
 * Server-side TLS
 */
struct tls_server_sys_t
{
798
799
    gnutls_certificate_credentials_t x509_cred;
    gnutls_dh_params_t               dh_params;
800
801
802

    struct saved_session_t          *p_cache;
    struct saved_session_t          *p_store;
803
804
    int                              i_cache_size;
    vlc_mutex_t                      cache_lock;
805

806
    int                            (*pf_handshake) (tls_session_t *);
807
808
809
};


810
811
812
/**
 * TLS session resumption callbacks (server-side)
 */
813
814
815
816
817
818
819
820
821
822
823
824
825
#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;


826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
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 )
{
854
    static const gnutls_datum_t err_datum = { NULL, 0 };
855
856
857
858
859
860
861
862
863
864
865
866
867
    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 ) )
        {
868
            gnutls_datum_t data;
869
870
871
872
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

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


920
921
922
923
924
/**
 * 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
925
gnutls_SessionClose (tls_server_t *p_server, tls_session_t *p_session)
926
927
{
    tls_session_sys_t *p_sys = p_session->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
928
    (void)p_server;
929
930
931
932
933
934
935
936
937
938
939
940

    if( p_sys->b_handshaked == VLC_TRUE )
        gnutls_bye( p_sys->session, GNUTLS_SHUT_WR );
    gnutls_deinit( p_sys->session );

    vlc_object_detach( p_session );
    vlc_object_destroy( p_session );

    free( p_sys );
}


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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
952
953
954
    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
955

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
956
957
958
959
960
961
962
    p_session->p_sys = malloc( sizeof(struct tls_session_sys_t) );
    if( p_session->p_sys == NULL )
    {
        vlc_object_destroy( p_session );
        return NULL;
    }

963
    p_server_sys = p_server->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
964
965
966
    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
967
968
    p_session->pf_set_fd = gnutls_SetFD;
    p_session->pf_handshake = p_server_sys->pf_handshake;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
969

970
971
    p_session->p_sys->b_handshaked = VLC_FALSE;
    p_session->p_sys->psz_hostname = NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
972

973
    i_val = gnutls_init( &session, GNUTLS_SERVER );
974
    if( i_val != 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
975
    {
976
        msg_Err( p_server, "cannot initialize TLS session: %s",
977
                 gnutls_strerror( i_val ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
978
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
979
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
980

981
    p_session->p_sys->session = session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
982

983
    if (gnutls_SessionPrioritize (VLC_OBJECT (p_session), session))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
984
    {
985
        gnutls_deinit( session );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
986
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
987
988
    }

989
    i_val = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE,
990
                                    p_server_sys->x509_cred );
991
    if( i_val < 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
992
    {
993
        msg_Err( p_server, "cannot set TLS session credentials: %s",
994
                 gnutls_strerror( i_val ) );
995
        gnutls_deinit( session );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
996
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
997
998
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
999
1000
    if (p_session->pf_handshake == gnutls_HandshakeAndValidate)
        gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUIRE);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1001

1002
    /* Session resumption support */
1003
    i_val = config_GetInt (p_server, "gnutls-cache-timeout");
1004
    gnutls_db_set_cache_expiration (session, i_val);
1005
1006
1007
1008
    gnutls_db_set_retrieve_function( session, cb_fetch );
    gnutls_db_set_remove_function( session, cb_delete );
    gnutls_db_set_store_function( session, cb_store );
    gnutls_db_set_ptr( session, p_server );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1009
1010

    return p_session;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1011
1012
1013
1014
1015
1016

error:
    free( p_session->p_sys );
    vlc_object_detach( p_session );
    vlc_object_destroy( p_session );
    return NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1017
1018
1019
}


1020
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1021
 * Adds one or more certificate authorities.
1022
1023
1024
1025
 *
 * @param psz_ca_path (Unicode) path to an x509 certificates list.
 *
 * @return -1 on error, 0 on success.
1026
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1027
1028
1029
static int
gnutls_ServerAddCA( tls_server_t *p_server, const char *psz_ca_path )
{
1030
    tls_server_sys_t *p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1031
1032
    char *psz_local_path;
    int val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1033

1034
1035
    p_sys = (tls_server_sys_t *)(p_server->p_sys);

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1036
    psz_local_path = ToLocale( psz_ca_path );
1037
    val = gnutls_certificate_set_x509_trust_file( p_sys->x509_cred,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1038
                                                  psz_local_path,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1039
                                                  GNUTLS_X509_FMT_PEM );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1040
    LocaleFree( psz_local_path );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1041
1042
    if( val < 0 )
    {
1043
        msg_Err( p_server, "cannot add trusted CA (%s): %s", psz_ca_path,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1044
                 gnutls_strerror( val ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1045
1046
        return VLC_EGENERIC;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1047
    msg_Dbg( p_server, " %d trusted CA added (%s)", val, psz_ca_path );
1048
1049

    /* enables peer's certificate verification */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1050
    p_sys->pf_handshake = gnutls_HandshakeAndValidate;
1051

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1052
1053
1054
1055
    return VLC_SUCCESS;
}


1056
/**
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1057
 * Adds a certificates revocation list to be sent to TLS clients.
1058
1059
1060
1061
1062
 *
 * @param psz_crl_path (Unicode) path of the CRL file.
 *
 * @return -1 on error, 0 on success.
 */