source: lib/ssl_gnutls.c @ ce402b2

Last change on this file since ce402b2 was 6b13103, checked in by dequis <dx@…>, at 2015-01-16T19:50:23Z

Replace isdigit/isalpha/.../tolower/toupper with glib variants

This fixes warnings about passing signed chars to them (apparently they
are implemented as macros that do array lookups without checks in some
platforms, yay)

Specifically:

functions=isalnum|isalpha|isdigit|isspace|isxdigit|tolower|toupper
sed -ir "s/$functions/g_ascii_&/g" /*.c

  • Property mode set to 100644
File size: 12.5 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., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  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 = 0;
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 && !g_ascii_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        /* This function returns false, so avoid calling b_event_remove again */
338        conn->inpa = -1;
339       
340        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
341        {
342                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
343                {
344                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
345                                                  ssl_handshake, data );
346                }
347                else
348                {
349                        conn->func( conn->data, 0, NULL, cond );
350                       
351                        gnutls_deinit( conn->session );
352                        closesocket( conn->fd );
353                       
354                        g_free( conn );
355                }
356        }
357        else
358        {
359                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
360                {
361                        conn->func( conn->data, stver, NULL, cond );
362
363                        gnutls_deinit( conn->session );
364                        closesocket( conn->fd );
365
366                        g_free( conn );
367                }
368                else
369                {
370                        /* For now we can't handle non-blocking perfectly everywhere... */
371                        sock_make_blocking( conn->fd );
372                       
373                        ssl_cache_add( conn );
374                        conn->established = TRUE;
375                        conn->func( conn->data, 0, conn, cond );
376                }
377        }
378       
379        return FALSE;
380}
381
382int ssl_read( void *conn, char *buf, int len )
383{
384        int st;
385       
386        if( !((struct scd*)conn)->established )
387        {
388                ssl_errno = SSL_NOHANDSHAKE;
389                return -1;
390        }
391       
392        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
393       
394        ssl_errno = SSL_OK;
395        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
396                ssl_errno = SSL_AGAIN;
397       
398        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
399       
400        return st;
401}
402
403int ssl_write( void *conn, const char *buf, int len )
404{
405        int st;
406       
407        if( !((struct scd*)conn)->established )
408        {
409                ssl_errno = SSL_NOHANDSHAKE;
410                return -1;
411        }
412       
413        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
414       
415        ssl_errno = SSL_OK;
416        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
417                ssl_errno = SSL_AGAIN;
418       
419        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
420       
421        return st;
422}
423
424int ssl_pending( void *conn )
425{
426        if( conn == NULL )
427                return 0;
428       
429        if( !((struct scd*)conn)->established )
430        {
431                ssl_errno = SSL_NOHANDSHAKE;
432                return 0;
433        }
434
435#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
436        if( ssl_errno == SSL_AGAIN )
437                return 0;
438#endif
439       
440        return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
441}
442
443void ssl_disconnect( void *conn_ )
444{
445        struct scd *conn = conn_;
446       
447        if( conn->inpa != -1 )
448                b_event_remove( conn->inpa );
449       
450        if( conn->established )
451                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
452       
453        closesocket( conn->fd );
454       
455        if( conn->session )
456                gnutls_deinit( conn->session );
457        g_free( conn->hostname );
458        g_free( conn );
459}
460
461int ssl_getfd( void *conn )
462{
463        return( ((struct scd*)conn)->fd );
464}
465
466b_input_condition ssl_getdirection( void *conn )
467{
468        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
469                B_EV_IO_WRITE : B_EV_IO_READ );
470}
471
472size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input,
473                         size_t input_len, const unsigned char *iv, unsigned char **res )
474{
475        gcry_cipher_hd_t gcr;
476        gcry_error_t st;
477       
478        ssl_init();
479       
480        *res = g_malloc( input_len  );
481        st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) ||
482             gcry_cipher_setkey( gcr, key, key_len ) ||
483             gcry_cipher_setiv( gcr, iv, 8 ) ||
484             gcry_cipher_encrypt( gcr, *res, input_len, input, input_len );
485       
486        gcry_cipher_close( gcr );
487       
488        if( st == 0 )
489                return input_len;
490       
491        g_free( *res );
492        return 0;
493}
Note: See TracBrowser for help on using the repository browser.