source: lib/ssl_gnutls.c @ e38c6d8

Last change on this file since e38c6d8 was 9b67285, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-12-24T19:17:37Z

SSL session caching. You'd think that this makes Twitter stuff faster, except
Twitter webservers seem to do successful session resumes only sporadically.

  • Property mode set to 100644
File size: 12.4 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* SSL module - GnuTLS version                                          */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#include <gnutls/gnutls.h>
27#include <gnutls/x509.h>
28#include <gcrypt.h>
29#include <fcntl.h>
30#include <unistd.h>
31#include "proxy.h"
32#include "ssl_client.h"
33#include "sock.h"
34#include "stdlib.h"
35#include "bitlbee.h"
36
37int ssl_errno = 0;
38
39static gboolean initialized = FALSE;
40gnutls_certificate_credentials_t xcred;
41
42#include <limits.h>
43
44#if defined(ULONG_MAX) && ULONG_MAX > 4294967295UL
45#define GNUTLS_STUPID_CAST (long)
46#else
47#define GNUTLS_STUPID_CAST (int)
48#endif
49
50#define SSLDEBUG 0
51
52struct scd
53{
54        ssl_input_function func;
55        gpointer data;
56        int fd;
57        gboolean established;
58        int inpa;
59        char *hostname;
60        gboolean verify;
61       
62        gnutls_session_t session;
63};
64
65static GHashTable *session_cache;
66
67static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond );
68static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond );
69static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond );
70
71static void ssl_deinit( void );
72
73static void ssl_log( int level, const char *line )
74{
75        printf( "%d %s", level, line );
76}
77
78void ssl_init( void )
79{
80        if( initialized )
81                return;
82       
83        gnutls_global_init();
84        gnutls_certificate_allocate_credentials( &xcred );
85        if( global.conf->cafile )
86        {
87                gnutls_certificate_set_x509_trust_file( xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM );
88               
89                /* Not needed in GnuTLS 2.11+ (enabled by default there) so
90                   don't do it (resets possible other defaults). */
91                if( !gnutls_check_version( "2.11" ) )
92                        gnutls_certificate_set_verify_flags( xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT );
93        }
94        initialized = TRUE;
95       
96        gnutls_global_set_log_function( ssl_log );
97        /*
98        gnutls_global_set_log_level( 3 );
99        */
100       
101        session_cache = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free );
102       
103        atexit( ssl_deinit );
104}
105
106static void ssl_deinit( void )
107{
108        gnutls_global_deinit();
109        gnutls_certificate_free_credentials( xcred );
110        g_hash_table_destroy( session_cache );
111        session_cache = NULL;
112}
113
114void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data )
115{
116        struct scd *conn = g_new0( struct scd, 1 );
117       
118        conn->func = func;
119        conn->data = data;
120        conn->inpa = -1;
121        conn->hostname = g_strdup( host );
122        conn->verify = verify && global.conf->cafile;
123        conn->fd = proxy_connect( host, port, ssl_connected, conn );
124       
125        if( conn->fd < 0 )
126        {
127                g_free( conn );
128                return NULL;
129        }
130       
131        return conn;
132}
133
134void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )
135{
136        struct scd *conn = g_new0( struct scd, 1 );
137       
138        conn->fd = fd;
139        conn->func = func;
140        conn->data = data;
141        conn->inpa = -1;
142        conn->hostname = g_strdup( hostname );
143       
144        /* For now, SSL verification is globally enabled by setting the cafile
145           setting in bitlbee.conf. Commented out by default because probably
146           not everyone has this file in the same place and plenty of folks
147           may not have the cert of their private Jabber server in it. */
148        conn->verify = verify && global.conf->cafile;
149       
150        /* This function should be called via a (short) timeout instead of
151           directly from here, because these SSL calls are *supposed* to be
152           *completely* asynchronous and not ready yet when this function
153           (or *_connect, for examle) returns. Also, errors are reported via
154           the callback function, not via this function's return value.
155           
156           In short, doing things like this makes the rest of the code a lot
157           simpler. */
158       
159        b_timeout_add( 1, ssl_starttls_real, conn );
160       
161        return conn;
162}
163
164static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond )
165{
166        struct scd *conn = data;
167       
168        return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
169}
170
171static int verify_certificate_callback( gnutls_session_t session )
172{
173        unsigned int status;
174        const gnutls_datum_t *cert_list;
175        unsigned int cert_list_size;
176        int gnutlsret;
177        int verifyret = 0;
178        gnutls_x509_crt_t cert;
179        struct scd *conn;
180       
181        conn = gnutls_session_get_ptr( session );
182
183        gnutlsret = gnutls_certificate_verify_peers2( session, &status );
184        if( gnutlsret < 0 )
185                return VERIFY_CERT_ERROR;
186
187        if( status & GNUTLS_CERT_INVALID )
188                verifyret |= VERIFY_CERT_INVALID;
189
190        if( status & GNUTLS_CERT_REVOKED )
191                verifyret |= VERIFY_CERT_REVOKED;
192
193        if( status & GNUTLS_CERT_SIGNER_NOT_FOUND )
194                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
195
196        if( status & GNUTLS_CERT_SIGNER_NOT_CA )
197                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
198
199        if( status & GNUTLS_CERT_INSECURE_ALGORITHM )
200                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
201
202#ifdef GNUTLS_CERT_NOT_ACTIVATED
203        /* Amusingly, the GnuTLS function used above didn't check for expiry
204           until GnuTLS 2.8 or so. (See CVE-2009-1417) */
205        if( status & GNUTLS_CERT_NOT_ACTIVATED )
206                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
207
208        if( status & GNUTLS_CERT_EXPIRED )
209                verifyret |= VERIFY_CERT_EXPIRED;
210#endif
211
212        if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 || gnutls_x509_crt_init( &cert ) < 0 )
213                return VERIFY_CERT_ERROR;
214
215        cert_list = gnutls_certificate_get_peers( session, &cert_list_size );
216        if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 )
217                return VERIFY_CERT_ERROR;
218
219        if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )
220        {
221                verifyret |= VERIFY_CERT_INVALID;
222                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
223        }
224
225        gnutls_x509_crt_deinit( cert );
226
227        return verifyret;
228}
229
230struct ssl_session
231{
232        size_t size;
233        char data[];
234};
235
236static void ssl_cache_add( struct scd *conn )
237{
238        size_t data_size;
239        struct ssl_session *data;
240        char *hostname;
241       
242        if( !conn->hostname || 
243            gnutls_session_get_data( conn->session, NULL, &data_size ) != 0 )
244                return;
245       
246        data = g_malloc( sizeof( struct ssl_session ) + data_size );
247        if( gnutls_session_get_data( conn->session, data->data, &data->size ) != 0 )
248        {
249                g_free( data );
250                return;
251        }
252       
253        hostname = g_strdup( conn->hostname );
254        g_hash_table_insert( session_cache, hostname, data );
255}
256
257static void ssl_cache_resume( struct scd *conn )
258{
259        struct ssl_session *data;
260       
261        if( conn->hostname &&
262            ( data = g_hash_table_lookup( session_cache, conn->hostname ) ) )
263        {
264                gnutls_session_set_data( conn->session, data->data, data->size );
265                g_hash_table_remove( session_cache, conn->hostname );
266        }
267}
268
269char *ssl_verify_strerror( int code )
270{
271        GString *ret = g_string_new( "" );
272       
273        if( code & VERIFY_CERT_REVOKED )
274                g_string_append( ret, "certificate has been revoked, " );
275        if( code & VERIFY_CERT_SIGNER_NOT_FOUND )
276                g_string_append( ret, "certificate hasn't got a known issuer, " );
277        if( code & VERIFY_CERT_SIGNER_NOT_CA )
278                g_string_append( ret, "certificate's issuer is not a CA, " );
279        if( code & VERIFY_CERT_INSECURE_ALGORITHM )
280                g_string_append( ret, "certificate uses an insecure algorithm, " );
281        if( code & VERIFY_CERT_NOT_ACTIVATED )
282                g_string_append( ret, "certificate has not been activated, " );
283        if( code & VERIFY_CERT_EXPIRED )
284                g_string_append( ret, "certificate has expired, " );
285        if( code & VERIFY_CERT_WRONG_HOSTNAME )
286                g_string_append( ret, "certificate hostname mismatch, " );
287       
288        if( ret->len == 0 )
289        {
290                g_string_free( ret, TRUE );
291                return NULL;
292        }
293        else
294        {
295                g_string_truncate( ret, ret->len - 2 );
296                return g_string_free( ret, FALSE );
297        }
298}
299
300static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
301{
302        struct scd *conn = data;
303       
304        if( source == -1 )
305        {
306                conn->func( conn->data, 0, NULL, cond );
307                g_free( conn );
308                return FALSE;
309        }
310       
311        ssl_init();
312       
313        gnutls_init( &conn->session, GNUTLS_CLIENT );
314        gnutls_session_set_ptr( conn->session, (void *) conn );
315#if GNUTLS_VERSION_NUMBER < 0x020c00
316        gnutls_transport_set_lowat( conn->session, 0 );
317#endif
318        gnutls_set_default_priority( conn->session );
319        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, xcred );
320        if( conn->hostname && !isdigit( conn->hostname[0] ) )
321                gnutls_server_name_set( conn->session, GNUTLS_NAME_DNS,
322                                        conn->hostname, strlen( conn->hostname ) );
323       
324        sock_make_nonblocking( conn->fd );
325        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd );
326       
327        ssl_cache_resume( conn );
328       
329        return ssl_handshake( data, source, cond );
330}
331
332static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
333{
334        struct scd *conn = data;
335        int st, stver;
336       
337        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
338        {
339                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
340                {
341                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
342                                                  ssl_handshake, data );
343                }
344                else
345                {
346                        conn->func( conn->data, 0, NULL, cond );
347                       
348                        gnutls_deinit( conn->session );
349                        closesocket( conn->fd );
350                       
351                        g_free( conn );
352                }
353        }
354        else
355        {
356                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
357                {
358                        conn->func( conn->data, stver, NULL, cond );
359
360                        gnutls_deinit( conn->session );
361                        closesocket( conn->fd );
362
363                        g_free( conn );
364                }
365                else
366                {
367                        /* For now we can't handle non-blocking perfectly everywhere... */
368                        sock_make_blocking( conn->fd );
369                       
370                        ssl_cache_add( conn );
371                        conn->established = TRUE;
372                        conn->func( conn->data, 0, conn, cond );
373                }
374        }
375       
376        return FALSE;
377}
378
379int ssl_read( void *conn, char *buf, int len )
380{
381        int st;
382       
383        if( !((struct scd*)conn)->established )
384        {
385                ssl_errno = SSL_NOHANDSHAKE;
386                return -1;
387        }
388       
389        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
390       
391        ssl_errno = SSL_OK;
392        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
393                ssl_errno = SSL_AGAIN;
394       
395        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
396       
397        return st;
398}
399
400int ssl_write( void *conn, const char *buf, int len )
401{
402        int st;
403       
404        if( !((struct scd*)conn)->established )
405        {
406                ssl_errno = SSL_NOHANDSHAKE;
407                return -1;
408        }
409       
410        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
411       
412        ssl_errno = SSL_OK;
413        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
414                ssl_errno = SSL_AGAIN;
415       
416        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
417       
418        return st;
419}
420
421int ssl_pending( void *conn )
422{
423        if( conn == NULL )
424                return 0;
425       
426        if( !((struct scd*)conn)->established )
427        {
428                ssl_errno = SSL_NOHANDSHAKE;
429                return 0;
430        }
431
432#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
433        if( ssl_errno == SSL_AGAIN )
434                return 0;
435#endif
436       
437        return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
438}
439
440void ssl_disconnect( void *conn_ )
441{
442        struct scd *conn = conn_;
443       
444        if( conn->inpa != -1 )
445                b_event_remove( conn->inpa );
446       
447        if( conn->established )
448                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
449       
450        closesocket( conn->fd );
451       
452        if( conn->session )
453                gnutls_deinit( conn->session );
454        g_free( conn->hostname );
455        g_free( conn );
456}
457
458int ssl_getfd( void *conn )
459{
460        return( ((struct scd*)conn)->fd );
461}
462
463b_input_condition ssl_getdirection( void *conn )
464{
465        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
466                B_EV_IO_WRITE : B_EV_IO_READ );
467}
468
469size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input,
470                         size_t input_len, const unsigned char *iv, unsigned char **res )
471{
472        gcry_cipher_hd_t gcr;
473        gcry_error_t st;
474       
475        ssl_init();
476       
477        *res = g_malloc( input_len  );
478        st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) ||
479             gcry_cipher_setkey( gcr, key, key_len ) ||
480             gcry_cipher_setiv( gcr, iv, 8 ) ||
481             gcry_cipher_encrypt( gcr, *res, input_len, input, input_len );
482       
483        gcry_cipher_close( gcr );
484       
485        if( st == 0 )
486                return input_len;
487       
488        g_free( *res );
489        return 0;
490}
Note: See TracBrowser for help on using the repository browser.